如何在Matplotlib中减小竖线(vlines)间距并修复边缘竖线异常问题
如何在Matplotlib中减小竖线(vlines)间距并修复边缘竖线异常问题
嘿,我仔细看了你的问题和代码,你现在用vlines模拟条形图的效果,但遇到了两个核心问题:一是类别间的间距没法缩小,二是边缘的线条贴边甚至消失。其实问题根源在于你用了类别型X轴,Matplotlib对这种轴的间距和边缘内边距是自动控制的,没法精细调整。下面给你一套针对性的解决方案:
问题核心原因
当X轴是字符串类别时,Matplotlib会给每个类别分配固定的“位置槽”,槽与槽之间的间距是默认设置的,而且边缘的槽直接对齐轴的边界,没有额外留白,所以会出现你看到的边缘线条贴边、间距没法缩小的情况。
解决方案步骤
我们可以把类别型X轴转换成数值型X轴,这样就能精确控制每个竖线的位置、宽度和整体的轴范围,完美解决你的两个问题:
- 给每个Variant分配一个数值索引(比如0、1、2、3),用这个数值来定位竖线
- 自定义竖线的视觉宽度和类别间的间距
- 手动设置X轴范围,给边缘留出和内部一致的间距
- 修正代码里的列名错误(原DataFrame里是
Rating,你代码里写成了Evaluation,这会导致运行报错)
修改后的完整代码
import pandas as pd import numpy as np import matplotlib import matplotlib.pyplot as plt import matplotlib.lines as mlines data = { "Variant": [ "Variant 1: Photovoltaic system", "Variant 1: Photovoltaic system", "Variant 1: Photovoltaic system", "Variant 1: Photovoltaic system", "Variant 1: Photovoltaic system", "Variant 2: Onshore wind turbine", "Variant 2: Onshore wind turbine", "Variant 2: Onshore wind turbine", "Variant 2: Onshore wind turbine", "Variant 2: Onshore wind turbine", "Variant 3: Offshore wind turbine", "Variant 3: Offshore wind turbine", "Variant 3: Offshore wind turbine", "Variant 3: Offshore wind turbine", "Variant 3: Offshore wind turbine", "Variant 4: Geothermal plant", "Variant 4: Geothermal plant", "Variant 4: Geothermal plant", "Variant 4: Geothermal plant", "Variant 4: Geothermal plant" ], "Type": ["Utility value"] * 20, "Person": [ "Person 1", "Person 2", "Person 3", "Person 4", "Person 5", "Person 1", "Person 2", "Person 3", "Person 4", "Person 5", "Person 1", "Person 2", "Person 3", "Person 4", "Person 5", "Person 1", "Person 2", "Person 3", "Person 4", "Person 5" ], "Criterion": ["Overall utility value"] * 20, "Rating": [ 370, 510, 470, 625, 750, 475, 605, 500, 665, 790, 710, 600, 715, 575, 700, 370, 350, 100, 585, 100 ] } # Create the DataFrame: df = pd.DataFrame(data) df_2 = df # Set colors: palette = ["#007CAA", "#A8005C", "#AED3E7", "#7B0040", "#00597A", "#55A930", "#D50C2F"] # Function to insert a line break after the second space: def split_label(label): words = label.split(" ") if len(words) <= 2: # If the criterion name consists of two or fewer words, make no changes return label else: # First part: the first two words first_part = " ".join(words[:2]) # Second part: the rest of the criterion name second_part = " ".join(words[2:]) # Combine the parts with a line break return first_part + "\n" + second_part # Split criterion names after every second space: df_2["Variant"] = df_2["Variant"].apply(split_label) # Create a list of criteria and exclude "Overall utility value": criteria_2 = df_2["Variant"].unique().tolist() # Mapping between criteria and colors: color_mapping_2 = dict(zip(criteria_2, palette)) # Overall utility value: # Filter by specific conditions: other_data_all = df_2[ (df_2["Type"] == "Utility value") & (df_2["Person"] != "Person 1") & (df_2["Criterion"] == "Overall utility value") ] own_data_all = df_2[ (df_2["Type"] == "Utility value") & (df_2["Person"] == "Person 1") & (df_2["Criterion"] == "Overall utility value") ] # ------------------- 关键修改部分开始 ------------------- # 为每个Variant分配数值型索引,用于精确控制X轴位置 variant_indices = np.arange(len(criteria_2)) # 自定义竖线的视觉宽度(控制间距:值越大,间距越小) bar_width = 0.9 # Group by criterion and calculate min and max: # 注意:这里把原来的"Evaluation"改成了DataFrame里实际的列名"Rating" other_grouped_data_all = other_data_all.groupby("Variant")["Rating"].agg(["min", "max"]).reset_index() # Ensure "grouped" contains the criteria in the same order as "criteria_2": other_grouped_data_all = other_grouped_data_all.set_index("Variant").loc[criteria_2].reset_index() # Assign colors to the vertical lines based on the criteria: line_colors_all = other_grouped_data_all["Variant"].map(color_mapping_2).tolist() # ------------------- 关键修改部分结束 ------------------- # Create the plot: plt.figure(figsize=(16, 7)) # Set Y-axis limits: y_limits_all = [] y_limits_all.append(other_grouped_data_all["min"].min()) y_limits_all.append(other_grouped_data_all["max"].max()) y_limits_all.append(own_data_all["Rating"].min()) y_limits_all.append(own_data_all["Rating"].max()) plt.ylim(min(y_limits_all) - 0.05 * min(y_limits_all), max(y_limits_all) + 0.05 * max(y_limits_all)) # Scatter plot for Person 1's ratings: # 这里的X轴用数值索引variant_indices,对应每个Variant的位置 plt.scatter( variant_indices, own_data_all["Rating"], color="#FFCC00", s=500, label="Person 1", zorder=3 ) # Display text to the right of the points: for x, y in zip(variant_indices, own_data_all["Rating"]): plt.text( x, y, f"{y:.2f}", fontsize=15, ha="left", va="center", color="#FFCC00", transform=plt.gca().transData + matplotlib.transforms.ScaledTranslation(15 / 72, 0, plt.gcf().dpi_scale_trans), # Background color: bbox=dict( facecolor="darkgrey", edgecolor="none", boxstyle="round, pad=0.1" ) ) # Vertical lines for the rating range of other people: plt.vlines( x=variant_indices, ymin=other_grouped_data_all["min"], ymax=other_grouped_data_all["max"], colors=line_colors_all, linewidth=80 * bar_width, # 根据bar_width调整竖线宽度,保持视觉比例 label="Other people", zorder=2 ) # Display text for min/max values above/below the bars: for x, ymin, ymax in zip(variant_indices, other_grouped_data_all["min"], other_grouped_data_all["max"]): # Max value: plt.text( x, ymax + 10, # 调整偏移量,避免和线条重叠 f"{ymax:.2f}", fontsize=15, ha="center", va="bottom" ) # Min value: plt.text( x, ymin - 10, f"{ymin:.2f}", fontsize=15, ha="center", va="top" ) # Add title and axis labels: plt.title("Overall Utility Value per Variant", fontsize=25) plt.xlabel("Variant", fontsize=25) plt.ylabel("Rating", fontsize=25) # 设置X轴刻度:用数值索引对应回原来的Variant标签 plt.xticks(variant_indices, criteria_2, fontsize=18) # 设置X轴范围,给边缘留出和内部一致的间距,避免线条贴边 plt.xlim(variant_indices[0] - (1 - bar_width)/2 - 0.05, variant_indices[-1] + (1 - bar_width)/2 + 0.05) # Create custom legend entries: scatter_handle = mlines.Line2D([], [], color="#FFCC00", marker="o", linestyle="None", markersize=8, label="Person 1") vline_handle = mlines.Line2D([], [], color="#A8005C", marker="|", linestyle="None", markersize=18, markeredgewidth=13, label="Other People") # Add legend with custom handles: plt.legend(handles=[scatter_handle, vline_handle], fontsize=12) # Axis adjustments and grid: plt.grid(axis="y", linestyle="--", alpha=0.7) plt.show()
额外说明
- 你可以通过调整
bar_width来控制竖线的间距:值越接近1,间距越小;值越小,间距越大 - Y轴的偏移量我也做了微调(比如
ymax +10),避免数值标签和竖线重叠 - 修正了代码里的列名错误,确保代码能正常运行
备注:内容来源于stack exchange,提问作者Samuel Vranici
相关产品推荐
相关产品推荐

