Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a bar chart whose bars are percent progress filled relative to a maximum or minimum

I have mechanical property data of a material having undergone heat treatment cycles. The bar chart is grouped by each cycle with the three properties: yield, tensile, and elongation. Yield and tensile strength share the same y axis while elongation is on a second-y axis. Yield and tensile strength have maximum values while elongation has a minimum. Rather than use reference lines for the min and max values, I would like the bars to filled as a percentage of the max min value.

I am using pandas to create a dataframe with .plot while using color=None and edgecolor to make "empty" bars. However, edgecolor sets the color for each group.

I have tried hatch and fill but haven't had any luck figuring out how to get only a percent of it filled.

I also don't want df["y_norm"] to show up in each group. This column just holds the ratio the bar should be filled with color.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from io import StringIO

s=StringIO("""     Yield     UTS     Elongation
T1     10.5     25.3     30.2
T2     10.8     26.3     30.3
T3     11.0     26.5     30.2
T4     11.5     27.2     30.4
T5     20.1     30.2     22.3
T6     24.7     31.2     19.0
T7     19.0     27.1     19.6
T8     12.2     21.7     23.4
T9     8.00     18.3     31.4""")

Ymax=float(16.0) #Yield strength maximum limit
UTSmax=float(22.0) #Ultimate Tensile Strengh maximum limit
Elmin=float(22.0) #Percent Elongation minimum limit

df = pd.read_csv(s, index_col=0, delimiter=' ',skipinitialspace=True)

df_scale=pd.DataFrame()
df_scale["y_norm"]=round(Ymax/df["Yield"],2)
df_scale["y_norm"]=df_scale["y_norm"].where(df_scale["y_norm"] < 1, 1)

#--progres bar for Ultimate Tensile Strength Maximum Limit-----
df_scale["T_norm"]=round(UTSmax/df["UTS"],2)
df_scale["T_norm"]=df_scale["T_norm"].where(df_scale["T_norm"]<1,1)

#--progres bar for Elongation Minimum Limit-----
df_scale["El_norm"]=round(Elmin/df["Elongation"],2)
df_scale["El_norm"]=df_scale["El_norm"].where(df_scale["El_norm"]>1,1)
df_scale["El_norm"]=df_scale["El_norm"].where(df_scale["El_norm"] 
<1,abs(df_scale["El_norm"]-2))

M_props=df.plot(kind="bar",secondary_y="Elongation",rot=0, 
edgecolor="rgb",color="None")
M_props.set_ylabel('KSI',rotation=0)
M_props.right_ax.set_ylabel('% El')
M_props.right_ax.set_ylim([10,40])

plt.show()

The results should show only the last two groups, T8 and T9, as completely filled bars.

like image 951
AKA_Tom Avatar asked Jan 23 '26 12:01

AKA_Tom


1 Answers

IIUC, you need to multiply the y_norm again and redraw:

# columns to draw:
col2draw = df.columns[:-1]

fig, M_props = plt.subplots(figsize=(10,6))

# draw the full-length empty bars
df[col2draw].plot(kind="bar",secondary_y="Elongation",
                         rot=0, edgecolor="k", ax=M_props,
                         color="None",
                         legend=False)

# fill the bars based on y_norm
(df[col2draw].apply(lambda x: x*df['y_norm'])
                    .plot(kind="bar",
                          secondary_y="Elongation",
                          rot=0,
                          ax=M_props)
)

M_props.set_ylabel('KSI',rotation=0)
M_props.right_ax.set_ylabel('% El')
M_props.right_ax.set_ylim([10,40])
plt.show()

Output:

enter image description here


Update: If you have different scales for the columns:

# create a scale dataframe, note the columns and index
df_scale = pd.DataFrame(columns=df.columns,
                        index=df.index)

# compute the scales, here we assign it randomly with seed
np.random.seed(1)
df_scale = np.random.uniform(0.5,1,(len(df),3))

# plot as previously
fig, M_props = plt.subplots(figsize=(10,6))

# draw the full-length empty bars
df.plot(kind="bar",secondary_y="Elongation",
                         rot=0, edgecolor="k", ax=M_props,
                         color="None",
                         legend=False)

# fill the bars based on y_norm
((df*df_scale).plot(kind="bar",
                  secondary_y="Elongation",
                  ax=M_props)
)

M_props.set_ylabel('KSI',rotation=0)
M_props.right_ax.set_ylabel('% El')
M_props.right_ax.set_ylim([10,40])
plt.show()

Output: enter image description here

like image 162
Quang Hoang Avatar answered Jan 26 '26 03:01

Quang Hoang



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!