fib-international / structuralcodes

A Python library for structural engineering calculations
https://fib-international.github.io/structuralcodes/
Apache License 2.0
108 stars 22 forks source link

first attempt of plot #177

Open talledodiego opened 2 months ago

talledodiego commented 2 months ago

This is just a first attempt.

The following code creates the related plot:

# Try new plot functionality
res_1 = section.section_calculator.calculate_nm_interaction_domain(theta=0)
res_2 = section.section_calculator.calculate_nm_interaction_domain(theta=0+np.pi)
fig, ax = res_1.get_2d_plot(vertical_axis='my', horizontal_axis='n')
fig, ax = res_2.get_2d_plot(ax=ax, vertical_axis='my', horizontal_axis='n')

image

Even this simple case it shows that is somehow complicated to do something that covers all common cases. For instance:

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta = np.linspace(0,np.pi,9)
for th in theta:
    res = section.section_calculator.calculate_nm_interaction_domain(theta=th)
    ax.plot(res.n*1e-3, res.m_y*1e-6, res.m_z*1e-6)
    res = section.section_calculator.calculate_nm_interaction_domain(theta=th + np.pi)
    ax.plot(res.n*1e-3, res.m_y*1e-6, res.m_z*1e-6)
ax.set_xlim(xmin=section.section_calculator.n_min*1e-3,xmax=section.section_calculator.n_max*1e-3)
ax.set_ylim(ymin=-600, ymax=600)
ax.set_zlim(zmin=-600, zmax=600)
ax.set_xlabel('N [kN]')
ax.set_ylabel('My [kNm]')
ax.set_zlabel('Mz [kNm]')

which produces the following plot: image

For dealing with this issue I created a function get_2d_plot and a function get_3d_plot that contains a 3d version of it. Alternatively we could create a single get_plot function and ask instead of vertical_axis and horizontal_axis the arguments x_axis and y_axis adding an Optional z_axis by default set to None. If specified it will create the 3d plot, otherwise the 2d plot.

In this way we can create for instance the following plot:

# Try new plot functionality
res_1 = section.section_calculator.calculate_nm_interaction_domain(theta=np.pi/3)
res_2 = section.section_calculator.calculate_nm_interaction_domain(theta=np.pi/3+np.pi)
fig, ax = res_1.get_3d_plot(x_axis='n', y_axis='my', z_axis='mz')
fig, ax = res_2.get_3d_plot(ax=ax, x_axis='n', y_axis='my', z_axis='mz')

# Customize the plot as desired
# Unfortunately there is no the equivalent of 2d axvline or axhline for 3d plots:

# Get the limits of the plot
x_lim = ax.get_xlim()
y_lim = ax.get_ylim()
z_lim = ax.get_zlim()

# Plot reference lines for the axes
ax.plot(x_lim, [0, 0], [0, 0], color='red')  # X-axis
ax.plot([0, 0], y_lim, [0, 0], color='green')  # Y-axis
ax.plot([0, 0], [0, 0], z_lim, color='blue')  # Z-axis

# Set the limits of the plot
ax.set_xlim(x_lim)
ax.set_ylim(y_lim)
ax.set_zlim(z_lim)

ax.xaxis.set_inverted(True)

producing the following picture:

image

res_1 = section.section_calculator.calculate_nm_interaction_domain(theta=0)
res_2 = section.section_calculator.calculate_nm_interaction_domain(theta=0+np.pi)

fig_mn, ax_mn = plt.subplots()
ax_mn.plot(res_1.n / 1000, res_1.m_y *1e-6)
ax_mn.plot(res_2.n / 1000, res_2.m_y *1e-6)
ax_mn.set_xlabel('N [kN]')
ax_mn.set_ylabel('M [kNm]')
ax_mn.axvline(0.0, linestyle='--', color ='k')
ax_mn.axhline(0.0, linestyle='--', color ='k')
ax_mn.set_xlim([-6500, 1500])
ax_mn.xaxis.set_inverted(True)

which produces the following picture: image

for this last problem maybe we can insert scale_x and scale_y arguments to the function get_plot?

What do you think @mortenengen ?

mortenengen commented 2 months ago

Thanks for drafting this, @talledodiego 😃 I totally see your point that it is not straightforward to implement some general methods that could fit all use cases. However, I think what you demonstrate is a good first step.

We could consider an option where we provide some meaningful simple methods that return plots that can be further customized by the user. This way, and when having already a section defined, a user can do a calculation, and get a plot in just two lines of code. For the special case with 3d plot of the interaction domain, we are perhaps targeting an expert user that in any case will implement his/her own plotting methods?

Or we could consider another option where we create a separate library which is only responsible for plotting the contents of the result classes in structuralcodes. This way we separate the concerns and build further on the initial idea that structuralcodes is mainly a backend for calculations, and other libraries or applications are responsible for the frontend.

talledodiego commented 2 months ago

Or we could consider another option where we create a separate library which is only responsible for plotting the contents of the result classes in structuralcodes. This way we separate the concerns and build further on the initial idea that structuralcodes is mainly a backend for calculations, and other libraries or applications are responsible for the frontend.

I think I agree with this point. I am in favour of splitting responsibilities to several packages. Now that you mention it, for instance OpenSeesPy is just a computation backend. For visualizing there is vfo.

Stated that, I agree with your idea of having a basic plot with just a line of code. And indeed is the idea of implementation where all arguments are optional. Therefore the user can just write:

res_1 = section.section_calculator.calculate_nm_interaction_domain(theta=0)
res_2 = section.section_calculator.calculate_nm_interaction_domain(theta=0+np.pi)
fig, ax = res_1.get_2d_plot()
fig, ax = res_2.get_2d_plot(ax=ax)

and obtain a basic diagram. Anyhow there are some issues that the basic user would like to handle, like scaling, coloring of lines, label names, etc.

mortenengen commented 2 months ago

Thanks for the clarification. This now grows into a larger question related to what the scope of structuralcodes should really be, and for the sake of maintenance I think factoring plotting out into a separate library makes most sense. Let us bring this up in a discussion with the rest of the group in our next meeting.

Meanwhile, let us keep the PR open.