I am trying to plot a series (length n) of tuples (low, high) as vertical lines (length = high-low) on a plotting area which must be 3n pixels wide. Each vertical line must only have a width of 1 pixel and the spacing between the lines must be exactly 2 pixels. However, the vertical lines are not equally spaced.
What I was able to produce so far:
The problem arises in the last point. The spacing between the lines is not consistently 2 pixel, probably since the defined plot size defines the size of the whole figure and not only the size of the main plotting area.
I already tried the following post:
Given a sequence of 7 tuples (n=7), each tuple being of the form (low, high) I would like to plot a figure 21 (3n) pixels wide. Thus, the plot should contain 7 vertical lines, each stretching from the low to high value on the y-axis. The axes should be excluded in the final plot. The x-positions for the lines should be at pixel 2,5,8,11,14,17 and 20.
The problem is, that matplotlib does not seem to preserve the defined spacing, probably due to the implicitly added boundary around the figure (see picture below).
The code at the end generates this graph (zoomed png):
The plot shows which problems should be solved:
from pathlib import Path
import matplotlib.pyplot as plt
from ctypes import windll
import numpy as np
plot_path = Path("./plots/testing/")
plot_path.mkdir(parents=True, exist_ok=True)
SYS_DPI = windll.user32.GetDpiForSystem()
print(f"Recognized DPI: {SYS_DPI}")
# create example dataset
example_series = [(1.3, 4.2), (5.3, 8.9), (4.0, 5.5),
(6, 7.8), (3.2, 4.5), (5.1, 8.1), (3, 8)]
width_pixel = len(example_series)*3
height_pixel = 10
# define xlims
y_min_vals = [x[0] for x in example_series]
y_vals_vals = [x[1] for x in example_series]
min_y, max_y = min(y_min_vals), max(y_vals_vals)
# define x positions and xlims
x_pos = np.arange(0, width_pixel, step=3)+2 # 2,5,8,11,14,17,20
min_x, max_x = 0, width_pixel
# create plot of needed size
fix, ax = plt.subplots(
figsize=(width_pixel/SYS_DPI, height_pixel/SYS_DPI), dpi=SYS_DPI)
ax.set_facecolor("white")
ax.set_axis_off()
ax.set_xlim((min_x, max_x))
ax.set_ylim(min_y, max_y)
for idx, val in enumerate(example_series):
print(f"{idx}: values: {val}")
ax.plot([x_pos[idx], x_pos[idx]], [val[0], val[1]],
color="black", linewidth=1)
plt.savefig(plot_path/"test.png",
bbox_inches=None,
pad_inches=0,
transparent=False,
dpi=SYS_DPI,
facecolor="white")
I obtain picture you want by changing aspect ratio of the axes to aspect ratio of figure and setting relative axes position to (0,0,1,1).
Enlarging screenshot of what i get (resolution 21*10 pixels)
example_series = [(1.3, 4.2), (5.3, 8.9), (4.0, 5.5),
(6, 7.8), (3.2, 4.5), (5.1, 8.1), (3, 8)]
width_pixel = len(example_series)*3
height_pixel = 10
dpi = 72 # random dpi because i don't have windows. Work with other values too.
bottom=np.array([x[0] for x in example_series])
top = np.array([x[1] for x in example_series])
height = top - bottom
min_y, max_y = min(bottom), max(top)
min_x, max_x = 0, width_pixel
x_pos = np.array([2,5,8,11,14,17,20])-1
fig_width = width_pixel/dpi
fig_height = height_pixel/dpi
fig, ax = plt.subplots(figsize=(fig_width, fig_height), dpi=dpi)
ax.set_xlim(min_x, max_x)
ax.set_ylim(min_y, max_y)
# WHY IT WORK
ax.set_box_aspect(fig_height/fig_width)
ax.set_position([0.0,0.0,1.0,1.0])
ax.set_axis_off()
# Bar instead of lines. It looks more clean for me
ax.bar(x_pos, height, width=1.0, bottom=bottom, align='edge', color='black')
plt.savefig('test.png')