stress strain curve

In this post, you'll see how to add an inset curve to a Matplotlib plot. An inset curve is a small plot laid on top of a main larger plot. The inset curve is smaller than the main plot and typically shows a "zoomed in" region of the main plot.

This is the second post in a series of posts on how to plot stress-strain curves with Python and Matplotlib. In the previous post in the series we built a stress-strain curve from the data in two .xls data files.

The plot built in the previous post included two lines, axes labels, a title, and legend. The data in the plot came from two .xls excel files and was from a tensile test of two metal samples. We are going to build upon that plot by adding an inset curve to it.

Pre-requisits

In order to build an inset curve with Matplotlib, you'll need to have a couple of things in place:

  • Computer
  • Python
  • Matplotlib, NumPy, and Pandas
  • Jupyter notebook (optional)
  • Data

Computer - to recreate the inset curve in this post, you'll need a computer. A laptop or desktop computer will both work. While it's possible to create the inset curve with a Chromebook, tablet, or phone - that's a bit more complicated and beyond the scope of this post.

Python - You need to have Python installed on your computer. I recommend installing the Anaconda Distribution of Python. See this post to see how to install Anaconda on your computer.

Matplotlib, NumPy, and Pandas - We'll build our inset curve using a Python plotting library called Matplotlib. Matplotlib is one of the most popular Python plotting libraries. If you installed Anaconda, Matplotlib is already installed. If you installed Python from somewhere else, you'll need to install Matplotlib. NumPy is a Python library for numeric computation. We'll use NumPy to make a few calculations. Pandas, a Python library for working with tabular data, will help us bring the data into our Python program.

You can install Matplotlib, NumPy, and Pandas with the Anaconda Prompt using the commands below:

> conda install matplotlib numpy pandas

Alternatively, you can use a terminal and the Python package manager pip to install Matplotlib, NumPy, and Pandas.

$ pip install matplotlib numpy pandas

Jupyter notebook - This is optional, but you can enter the code in this post into a Jupyter notebook and see the plot produced in the same Jupyter notebook. Alternatively, you can enter the code into a .py file and run the .py file to produce the plot. As long as the Anaconda distribution of Python is installed on your computer, you use a Jupyter notebook. See how to open Jupyter notebook in this post.

Data - The data we are going to plot is from two .xls data files. You can download these files using the links below:

aluminum6061.xls

steel1045.xls

Move these .xls data files into the same folder on your computer as your Jupyter notebook or .py file that contains your code.

Once these pre-requisites are in place, it's time to start building our inset curve!

Build the main plot

The type of plot we are building is called a stress-strain curve. Stress-strain curves are used by engineers to demonstrate how a material deforms under stress. We are going to add an inset curve on top of our stress-strain curve that highlights the linear elastic region. An inset curve is a small plot that is placed on top of the main plot. Inset curves usually show a zoomed-in view of a larger plot. On our stress-strain curve, the inset curve will show a zoomed-in view of the linear elastic region on the plot.

To build our inset curve, we will use Python and Matplotlib's object-oriented interface. The plot from a previous post, was constructed using Matplotlib's pyplot interface. For a simple plot, the pyplot interface above is sufficient. But our plot with an inset curve is more complicated than just a plot with two lines- we are going to add an inset curve.

Matplotlib's object-oriented interface allows us to access individual components of the plot, like axes, and set specific attributes on these components.

Imports

To build our plot with an inset curve, first, we need to import a couple of packages: NumPy, Pandas, and Matplotlib. These three Python packages have common import aliases: np, pd, and plt. The line %matplotlib inline is a Jupyter notebook magic command that results in plots produced within the same Jupyter notebook (as opposed to the plot popping out in a new window). If you are coding your inset curve in a .py file, leave %matplotlib inline out, as it is not valid Python code.

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

The next few lines of code are not required, but can be helpful to others who might use our code again.

The lines of code below print out to version of Python and the versions of the packages we imported above.

In [2]:
import sys
import matplotlib

print(f"Python Version: {sys.version}")
print(f"NumPy Version: {np.__version__}")
print(f"Pandas Version: {pd.__version__}")
print(f"Matplotlib Version: {matplotlib.__version__}")
Python Version: 3.8.5 (default, Sep  3 2020, 21:29:08) [MSC v.1916 64 bit (AMD64)]
NumPy Version: 1.20.3
Pandas Version: 1.3.1
Matplotlib Version: 3.4.2

Ensure the two .xls data files are in the same folder as our code

The two external files that contain the data we are going to plot are linked below:

aluminum6061.xls

steel1045.xls

If you are following along and building an inset curve yourself, make sure to download the .xls data files and place these data files in the same directory as your Jupyter notebook or Python script.

A convenient Jupyter notebook magic command, %ls, lists the contents of the current directory (the directory where the notebook is saved). Make sure you can see the two .xls data files in this directory. The %ls command is not valid Python code, so if you are building your plot in a .py file, make sure to leave %ls out.

In [3]:
%ls
 Volume in drive C is OS
 Volume Serial Number is 1236-A93E

 Directory of C:\Users\peter.kazarinoff\Documents\staticsite\content\code\matplotlib_plots\stress_strain_curves

11/02/2021  09:39 AM    <DIR>          .
11/02/2021  09:39 AM    <DIR>          ..
03/21/2021  06:54 PM    <DIR>          .ipynb_checkpoints
12/28/2020  01:47 PM            36,864 aluminum6061.xls
12/28/2020  01:47 PM    <DIR>          images
12/28/2020  01:47 PM            38,912 steel1045.xls
11/02/2021  09:37 AM            39,284 stress_strain_curve_with_inset.png
11/02/2021  09:39 AM           126,300 stress_strain_curve_with_inset_elastic_region.ipynb
12/28/2020  01:47 PM            88,263 stress_strain_curve_with_python.ipynb
12/28/2020  01:47 PM           113,352 stress-strain_curve.png
               6 File(s)        442,975 bytes
               4 Dir(s)  40,949,649,408 bytes free

We can see the two data files aluminum6061.xls and steel1045.xls in the same folder as our Jupyter notebook stress_strain_curve_with_inset_elastic_region.ipynb

Next, we need to bring in the data from the two external .xls files into variables in our script.

Read the data with Pandas

Next, we'll bring the two .xls data files into our Python script using Panda's pd.read_excel() function. The result is two Pandas data frames called steel_df and al_df which contain the data from our two .xls files.

In [4]:
al_df = pd.read_excel("aluminum6061.xls")
steel_df = pd.read_excel("steel1045.xls")
WARNING *** OLE2 inconsistency: SSCS size is 0 but SSAT size is non-zero
WARNING *** OLE2 inconsistency: SSCS size is 0 but SSAT size is non-zero

We can use Panda's df.head() method to view the first five rows of each dataframe.

In [5]:
steel_df.head()
Out[5]:
TESTNUM POINTNUM TIME POSIT FORCE EXT CH5 CH6 CH7 CH8
0 762 1 5.969 0.01284 201.030792 0.001572 -0.007133 NaN NaN NaN
1 762 2 6.242 0.01392 215.235886 0.000009 -0.014581 NaN NaN NaN
2 762 3 6.936 0.01646 246.816742 -0.000832 0.006942 NaN NaN NaN
3 762 4 8.632 0.02340 371.870361 0.002203 0.000776 NaN NaN NaN
4 762 5 10.533 0.03110 502.501862 0.001481 0.018102 NaN NaN NaN
In [6]:
al_df.head()
Out[6]:
TESTNUM POINTNUM TIME POSIT FORCE EXT CH5 CH6 CH7 CH8
0 761 1 6.532 0.01524 201.158508 0.018893 -0.023081 NaN NaN NaN
1 761 2 6.702 0.01600 205.978119 0.000265 -0.013024 NaN NaN NaN
2 761 3 7.098 0.01720 219.295441 -0.000877 -0.024879 NaN NaN NaN
3 761 4 8.697 0.02350 268.505890 0.001453 -0.006798 NaN NaN NaN
4 761 5 10.196 0.03004 322.028168 0.001865 0.012563 NaN NaN NaN

We see a couple of columns in each data frame. The columns we are interested in are below:

  • FORCE Force measurements from the load cell in pounds (lb)
  • EXT Extension measurements from the mechanical extensometer in percent (%), strain in percent
  • CH5 Extension readings from the laser extensometer in percent (%), strain in percent

We will use these columns from the data frames in the following ways:

  • FORCE will be converted to stress using the cross-sectional area of our test samples
$$ \sigma = \frac{F}{A_0} $$
  • EXT (mechanical extensometer readings) will be converted into strain on our inset curve
$$ \epsilon_{unitless} = \epsilon_{\%} \times 0.01 $$
  • CH5 (laser extensometer readings) will be converted into strain on the overall, large stress-strain curve
$$ \epsilon_{unitless} = \epsilon_{\%} \times 0.01 $$
In [7]:
strain_al_plastic = al_df['CH5']*0.01
strain_al_elastic = al_df['EXT']*0.01
d_al = 0.506
stress_al = (al_df['FORCE']*0.001)/(np.pi*((d_al/2)**2))

strain_steel_plastic = steel_df['CH5']*0.01
strain_steel_elastic = steel_df['EXT']*0.01
d_steel = 0.506
stress_steel = (steel_df['FORCE']*0.001)/(np.pi*((d_steel/2)**2))

Build a simple plot

Now that we have the data in 6 panda's series, we can build a simple plot.

We'll use Matplotlib's object-oriented interface to create two objects, a figure object called fig and an axes object ax1. Then we can run the .plot() method on our ax1 object and pass in two sets of series. The command plt.show() shows the plot.

In [8]:
fig, ax1 = plt.subplots()
ax1.plot(strain_al_plastic,stress_al,strain_steel_plastic,stress_steel)
plt.show()

We see a plot (a stress-strain curve) with two lines. Right now, the plot looks pretty bare. We can spruce it up a bit with axis labels, a title, and a legend.

Add axis labels, a title, and a legend

Next, let's use Matplotlib's object-oriented interface to add axis labels and titles. Below is a chart that relates how to use Matplotlib's plt approach, compared to the object-oriented approach that we'll use.

plot component plt approach object-oriented approach
x-axis label plt.xlabel('strain') ax.set_xlabel('strain')
y-axis label plt.ylabel('stress') ax.set_ylabel('stress')
title plt.title('stress-strain curve') ax.set_title('stress-strain curve')
legend plt.legend(['Al6061','Steel1045']) ax.legend(['Al6061','Steel1045'])
In [9]:
fig, ax1 = plt.subplots()
fig.set_size_inches([9,9])
ax1.plot(strain_al_plastic,stress_al,strain_steel_plastic,stress_steel)
ax1.legend(['Al6061','Steel1045'])
ax1.set_xlabel('Strain (in/in)')
ax1.set_ylabel('Stress (ksi)')
ax1.set_title('Stress-Strain Curve of Al6061 and Steel1045 in Tension')

plt.show()

We see a plot with two lines, axis labels, a title, and a legend. Now, onto adding the inset curve!

Add the inset curve

Next, let's add our inset curve. Our inset curve is a zoomed-in view of the linear elastic region (the far left, almost vertical portion of the stress-strain curves). To create a plot on top of another plot (our inset curve), we'll use Matplotlib's fig.add_axes([ ]) method. This method outputs a new axis object ax2. ax2 will be the axes that we plot the inset curve on. We can set attributes like .set_xlabel() on our ax2 object to add labels to the axis, a title, and limit the x and y-axis so we only show the left-most portion of the curve.

In [10]:
fig, ax1 = plt.subplots()
fig.set_size_inches([9,9])

ax1.plot(strain_al_plastic,stress_al,strain_steel_plastic,stress_steel)
ax1.legend(['Al6061','Steel1045'])
ax1.set_xlabel('Strain (in/in)')
ax1.set_ylabel('Stress (ksi)')
ax1.set_title('Stress-Strain Curve of Al6061 and Steel1045 in Tension \n with inset curve of elastic region')

left, bottom, width, height = [0.5, 0.5, 0.25, 0.25]
ax2 = fig.add_axes([left, bottom, width, height])
ax2.plot(strain_al_elastic,stress_al,strain_steel_elastic, stress_steel)
ax2.set_xlabel('Strain (in/in)')
ax2.set_ylabel('Stress (ksi)')
ax2.set_title('Inset of Elastic Region')
ax2.set_xlim([0,0.008])
ax2.set_ylim([0,100])

fig.savefig('stress_strain_curve_with_inset.png')

plt.show()

We see a plot that contains an inset curve. The inset curve has two lines, just like the large plot. The inset curve has axis labels and a title. The same colors in the main plot are used in the inset plot. The inset curve shows a zoomed-in view of the larger plot. Note how the x-axis and y-axis limits of the main plot and the inset curve are different.

Summary

In this post, we plotted a stress-strain curve with Python and Matplotlib then added an inset curve on top of our main plot. We used Matplotlib's object-oriented interface to create the inset curve.

The code to remember if you want to add an inset curve on top of a Matplotlib plot is below:

fig, ax1 = plt.subplots()

ax1.plot(x,y)

left, bottom, width, height = [0.5, 0.5, 0.25, 0.25] # modify to move the inset curve and change its size
ax2 = fig.add_axes([left, bottom, width, height])
ax2.plot(x,y)
ax2.set_xlim([lower,upper])
ax2.set_ylim([lower,upper])

plt.show()

Support

Want to learn about building more plots with Matplotlib? Check out my book Problem Solving with Python on Amazon (Affiliate Link):