Python coding

Drawing with matplotlib

last updated: 2021-01-12

Short description

For my tutorials and my webpage I need drawings. For this I like to use Inkscape, first because it is open source and second because it is performant. The Inkscape files (.svg) are vector graphics and are fully scalable. They can be easily embedded in a webpage with:

    <center><img src="svg/my_drawing.svg" alt="example drawing" width=900></center>

For circuits I use KiCad also open source. KiCad plots to svg. For formulas I use KLatexFormula, exporting to svg. But to draw mathematical functions I needed a good solution exporting to svg, and I found that python 3 with the libraries numpy, matplotlib and sometimes scipy or pandas is a very good and cool solution. On this page I will toss some code snippets to remember them for later use.

6 subplots

measuring current with an oscilloscope

    import numpy as np
    import matplotlib.pyplot as plt
    import scipy.signal

    svg_path = "/savit/www.weigu/other_projects/python_coding/drawing_w_matplotlib/svg/"

    xaxis_label = "time t / ms"
    yaxis_label = "amplitude u(t) / V"
    svg_size_small = (7.5,12)
    svg_size_big = (20,30)

    # produce data
    t = np.linspace(0, 1, 1000, endpoint=True)
    u1 = 5*np.sin(2 * np.pi * 5 * t)
    u2 = 5*scipy.signal.square(2 * np.pi * 5 * t)
    u3 = 5*scipy.signal.sawtooth(2 * np.pi * 5 * t, 0.5)
    u4 = 5*scipy.signal.sawtooth(2 * np.pi * 5 * t)
    u5 = -5*scipy.signal.sawtooth(2 * np.pi * 5 * t)
    u6 = 5*np.ones(t.shape[0])
    u6[np.where(u1<4.3)] = -5

    # figure = drawing area
    fig = plt.figure(figsize=(svg_size_small),tight_layout=True)

    # 6 subplots, one for each curve
    ax1 = fig.add_subplot(611) # 6 rows, 1 col, fig1
    ax2 = fig.add_subplot(612) # 6 rows, 1 col, fig2
    ax3 = fig.add_subplot(613) # 6 rows, 1 col, fig3
    ax4 = fig.add_subplot(614) # 6 rows, 1 col, fig4
    ax5 = fig.add_subplot(615) # 6 rows, 1 col, fig5
    ax6 = fig.add_subplot(616) # 6 rows, 1 col, fig6

    ax1.set_title('sine wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax1.grid(True, which='both')
    ax1.set_xlabel(xaxis_label)
    ax1.set_ylabel(yaxis_label)

    ax2.set_title('square wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax2.grid(True, which='both')
    ax2.set_xlabel(xaxis_label)
    ax2.set_ylabel(yaxis_label)

    ax3.set_title('triangle wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax3.grid(True, which='both')
    ax3.set_xlabel(xaxis_label)
    ax3.set_ylabel(yaxis_label)

    ax4.set_title('sawtooth wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax4.grid(True, which='both')
    ax4.set_xlabel(xaxis_label)
    ax4.set_ylabel(yaxis_label)

    ax5.set_title('inverted sawtooth wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax5.grid(True, which='both')
    ax5.set_xlabel(xaxis_label)
    ax5.set_ylabel(yaxis_label)

    ax6.set_title(' PWM (Pulse Width Modulation) wave',fontsize = 12,
                  fontweight = "bold", verticalalignment = 'baseline')
    ax6.grid(True, which='both')
    ax6.set_xlabel(xaxis_label)
    ax6.set_ylabel(yaxis_label)

    l1, = ax1.plot(t, u1, 'b')
    l1, = ax2.plot(t, u2, 'b')
    l1, = ax3.plot(t, u3, 'b')
    l1, = ax4.plot(t, u4, 'b')
    l1, = ax5.plot(t, u5, 'b')
    l1, = ax6.plot(t, u6, 'b')

    fig.show()
    fig.savefig(svg_path + "6_waveforms.svg", format = "svg") #, transparent = "True")

Fourier (FFT)

spectrum of 3 signals

    import numpy as np
    import matplotlib.pyplot as plt
    import scipy.signal

    svg_path = "/savit/www.weigu/other_projects/python_coding/drawing_w_matplotlib/svg/"

    xaxis_label = "time t / ms"
    yaxis_label = "amplitude u(t) / V"
    xaxis_label_f = "frequency f / kHz"
    yaxis_label_f = "ampl. spectrum"

    svg_size_small = (7.5,12)
    svg_size_big = (20,30)

    n = 150
    sampling_period = 5
    interval = sampling_period / n

    # produce data
    t = np.linspace(0, 1, 1000, endpoint=True)
    u1 = 5*np.sin(2 * np.pi * 5 * t)
    tf = np.arange(0, sampling_period, interval)
    f = np.arange(n / 2) / (n * interval)
    u1f = 5*np.sin(2 * np.pi * 1 * tf)
    u1ff = np.fft.fft(u1f)
    u1fft = abs(u1ff[range(int(n / 2))] / n )

    u2 = 5*scipy.signal.square(2 * np.pi * 5 * t)
    u2f = 5*scipy.signal.square(2 * np.pi * 1 * tf)
    u2ff = np.fft.fft(u2f)
    u2fft = abs(u2ff[range(int(n / 2))] / n )

    u3 = 5*scipy.signal.sawtooth(2 * np.pi * 5 * t)
    u3f = 5*scipy.signal.sawtooth(2 * np.pi * 1 * tf)
    u3ff = np.fft.fft(u3f)
    u3fft = abs(u3ff[range(int(n / 2))] / n )

    # figure = drawing area
    fig = plt.figure(figsize=(svg_size_small),tight_layout=True)
    # 6 subplots, one for each curve
    ax1 = fig.add_subplot(611) # 6 rows, 1 col, fig1
    ax2 = fig.add_subplot(612) # 6 rows, 1 col, fig2
    ax3 = fig.add_subplot(613) # 6 rows, 1 col, fig3
    ax4 = fig.add_subplot(614) # 6 rows, 1 col, fig4
    ax5 = fig.add_subplot(615) # 6 rows, 1 col, fig5
    ax6 = fig.add_subplot(616) # 6 rows, 1 col, fig6

    ax1.set_title('sine wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax1.grid(True, which='both')
    ax1.set_xlabel(xaxis_label)
    ax1.set_ylabel(yaxis_label)

    ax2.set_title('spectrum of sine wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax2.grid(True, which='both')
    ax2.set_xlabel(xaxis_label_f)
    ax2.set_ylabel(yaxis_label_f)

    ax3.set_title('square wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax3.grid(True, which='both')
    ax3.set_xlabel(xaxis_label)
    ax3.set_ylabel(yaxis_label)

    ax4.set_title('spectrum of square wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax4.grid(True, which='both')
    ax4.set_xlabel(xaxis_label_f)
    ax4.set_ylabel(yaxis_label_f)

    ax5.set_title('sawtooth wave',fontsize = 12, fontweight = "bold",
                  verticalalignment = 'baseline')
    ax5.grid(True, which='both')
    ax5.set_xlabel(xaxis_label)
    ax5.set_ylabel(yaxis_label)

    ax6.set_title('spectrum of sawtooth wave',fontsize = 12,
                  fontweight = "bold", verticalalignment = 'baseline')
    ax6.grid(True, which='both')
    ax6.set_xlabel(xaxis_label_f)
    ax6.set_ylabel(yaxis_label_f)

    l1, = ax1.plot(t, u1, 'b')
    l1, = ax2.plot(f, u1fft, 'b')
    l1, = ax3.plot(t, u2, 'b')
    l1, = ax4.plot(f, u2fft, 'b')
    l1, = ax5.plot(t, u3, 'b')
    l1, = ax6.plot(f, u3fft, 'b')

    fig.show()
    fig.savefig(svg_path + "spectrum_x_3.svg", format = "svg") #, transparent = "True")

Logarithmic scale

rc low-pass frequency response 1-3 order

    import matplotlib.pyplot as plt
    import numpy as np
    # EDIT
    R1 = 22
    C1 = 2.2E-6  # fg= 3288
    R2 = 14.15
    C2 = 2.2E-6
    R3 = 11.2
    C3 = 2.2E-6
    svg_path = "/savit/www.weigu/other_projects/python_coding/drawing_w_matplotlib/svg/"
    # END EDIT
    # create equally spaced log(f) values array f
    flog = np.linspace(1.0, 6.0, 100)
    f = 10.0 ** flog
    # calculate F (complex) and absolute value Fabs
    RC1 = R1*C1
    RC2 = R2*C2
    RC3 = R3*C3
    jomega = 1j*2*np.pi*f
    F1 = 1 / (1 + jomega*RC1)
    F1abs = abs(F1)
    F2 = (1 / (1 + jomega*RC2)) ** 2
    F2abs = abs(F2)
    F3 = (1 / (1 + jomega*RC3)) ** 3
    F3abs = abs(F3)

    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(f, F1abs)
    ax.plot(f, F2abs)
    ax.plot(f, F3abs)
    ax.hlines(y=0.7071, xmin=0, xmax=1000000, linewidth=1, color='red')
    ax.vlines(x=3288, ymin=-0, ymax=1, linewidth=1, color='red')
    ax.margins(x=0)  # remove inner margins
    ax.text(10, 0.71, "0.7071", fontsize=10, color="red")
    ax.text(3100, -0.1, "fc", fontsize=10, color="red")
    ax.grid(True, which = "both", linestyle = "-")
    ax.set_xscale ("log")
    ax.set_xlabel("f/Hz")
    ax.set_ylabel("Uout/Uin")
    ax.set_title("RC low-pass frequency response 1, 2 and 3 order")
    fig.show()
    fig.savefig(svg_path + "rc_low_pass_frequency_response_1-3_order.svg", format = "svg") #, transparent = "True")

2 plots side by side

rc low-pass frequency response 1-3 order

    import matplotlib.pyplot as plt
    import numpy as np
    import matplotlib.ticker as ticker

    # EDIT
    R1 = 10000
    C1 = 100E-6
    U = 5
    svg_path = "/savit/www.weigu/other_projects/python_coding/drawing_w_matplotlib/svg/"
    # END EDIT

    RC1 = R1*C1
    t1 = np.linspace(0, 7, num=100)
    t2 = np.linspace(7, 14, num=100)
    u_c_c = 5*(1-np.exp(-t1/RC1))
    i_c_c = 5*(np.exp(-t1/RC1))
    u_c_d = 5*(np.exp(-t2+7/RC1))
    i_c_d = -5*(np.exp(-t2+7/RC1))
    tauline_c =5*t1
    tauline_d =-5*(t2-7)+5

    fig, (ax1,ax2) = plt.subplots(1, 2, dpi=300, figsize=(10,4))
    plt.subplots_adjust(wspace=0, hspace=0)
    ax1.plot(t1, u_c_c, color = 'blue')
    ax1.plot(t1, i_c_c, color = 'red')
    ax1.plot(t1, tauline_c, color = 'green',linewidth=0.7)
    ax1.hlines(y=5, xmin=0, xmax=7, linewidth=0.7, color='black')
    ax1.hlines(y=0, xmin=0, xmax=7, linewidth=1, color='black')
    ax1.hlines(y=-5, xmin=0, xmax=7, linewidth=0.7, color='black')
    ax1.hlines(y=3.15, xmin=0, xmax=2, linewidth=0.7, color='cyan')
    ax1.text(1.35, 3.25, "63%", fontsize=10, color="cyan")
    ax1.text(0.8, 5.05, "τ", fontsize=10, color="green")
    ax1.text(1.85, 5.05, "2τ", fontsize=10, color="green")
    ax1.text(2.85, 5.05, "3τ", fontsize=10, color="green")
    ax1.text(3.85, 5.05, "4τ", fontsize=10, color="green")
    ax1.text(4.85, 5.05, "5τ", fontsize=10, color="green")

    ax2.plot(t2, u_c_d, color = 'blue')
    ax2.plot(t2, i_c_d, color = 'red')

    ax2.hlines(y=5, xmin=7, xmax=14, linewidth=0.7, color='black')
    ax2.hlines(y=0, xmin=7, xmax=14, linewidth=1, color='black')
    ax2.hlines(y=-5, xmin=7, xmax=14, linewidth=0.7, color='black')
    ax2.text(8.1, 2.05, "37%", fontsize=10, color="cyan")
    ax2.plot(t2, tauline_d, color = 'green', linewidth=0.7)
    ax2.hlines(y=1.85, xmin=7, xmax=9, linewidth=0.7, color='cyan')
    ax2.text(8, 5.05, "τ", fontsize=10, color="green")
    ax2.text(8.85, 5.05, "2τ", fontsize=10, color="green")
    ax2.text(9.85, 5.05, "3τ", fontsize=10, color="green")
    ax2.text(10.85, 5.05, "4τ", fontsize=10, color="green")
    ax2.text(11.85, 5.05, "5τ", fontsize=10, color="green")

    ax1.margins(x=0)  # remove inner margins
    ax2.margins(x=0)
    ax1.grid(True, which = "both", linestyle = "-")
    ax2.grid(True, which = "both", linestyle = "-")

    ax1.set_ylim(-5.5,5.5)
    ax2.set_ylim(-5.5,5.5)
    ax1.set_yticks(np.arange(-5, 6, step=1)) # more ticks
    ax2.set_yticks(np.arange(-5, 6, step=1))

    ax1.set_ylabel("Uout/V", color="blue")
    ax1.set_title("Charging a capacitor with 5V (τ=1ms)")
    ax2.set_xlabel("t/ms")
    ax2.set_title("Discharging a capacitor (R=10kΩ, C=100nF)")

    ax3 = ax2.twinx()
    ax3.set_ylim(-5.5,5.5)
    ax3.set_yticks(np.arange(-5, 6, step=1)) # more ticks
    ax3.set_ylabel("I/mA", color="red")

    scale_y = 10  # Change only ax3 divide values by 10
    ticks_y = ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x/scale_y))
    ax3.yaxis.set_major_formatter(ticks_y)

    ax2.label_outer()

    fig.subplots_adjust(left=0.07, right=0.93, top=0.92, bottom=0.1)

    fig.show()
    fig.savefig(svg_path + "capacitor_charging_discharging_diagrams.svg", format = "svg") #, transparent = "True")