I want to create a subplot using matplotlib with pysimplegui where each time i select some signals-checkbox(list of values) i should get a plot corresponding to it which will dynamically increase or decrease in size based on my selection so when i select a checkbox the respective plot will be plotted and eqaully spaced and when i deselect a plot it should be gone and other plots should automatically occupy the space Programming Language – Python3 Matplotlib – 3.6.2
My Requirement:
- There will always be maximum 10 rows and 1 column in my subplot (matplotlib)
- So lets say iam making a call to “plt.show()” after making 1 plot my result should have only one plot at that moment occupying the whole figure
- Now iam adding one more plot and then calling “plt.show()”, now it should have 2 plots occupying the fig equally this would be the same for 3,4,5 … 10 plots but all these plots should have a single shared axis whichever is at the bottom
- And also a way to delete a plot at any instance of time so for instance i make a plot say axis1 and then I make a plot axis2 now I want axis1 to be deleted after which only my axis2 plot will be there occupying the whole figure
The below code satisfies most of my requirement but its just that the axis are not shared with each other and also the sizing of each plots are different
reff1: Changing matplotlib subplot size/position after axes creation reff2: Dynamically add/create subplots in matplotlib
import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec number_of_plots = 2 fig = plt.figure() ax = fig.add_subplot(1, 1, 1) gs = gridspec.GridSpec(number_of_plots + 1, 1) plot_space = gs[0:number_of_plots].get_position(fig) print(plot_space, [plot_space.x0, plot_space.y0, plot_space.width, plot_space.height]) ax.set_position(plot_space) ax.set_subplotspec(gs[0:number_of_plots]) # only necessary if using tight_layout() fig.tight_layout() # not strictly part of the question ax = fig.add_subplot(gs[2], sharex=ax) plt.show()
Advertisement
Answer
Here is a basis for the interactive part.
I remove all axes from the figure at each new selection and add the newly selected ones on a relevant GridSpec
.
Still axes are not redrawn every time thanks to @lru_cache
.
Edit: Added shared x axes
from functools import lru_cache import matplotlib.pyplot as plt import numpy as np from matplotlib.gridspec import GridSpec from matplotlib.ticker import FuncFormatter, NullFormatter plt.ion() NULL_FORMATTER = NullFormatter() FUNC_FORMATTER = FuncFormatter(lambda x, pos: f"{x:,.0f}") def main(): # Emulate data and user selection np.random.seed(0) datas = generate_data(num_datas=10) states = generate_states(list(datas.keys()), num_states=6, max_key_per_state=5) fig = plt.figure(figsize=(6, 8)) @lru_cache def get_data_ax(data_id): """Create a new ax on fig with corresponding data plotted""" # thanks to @lru_cache, axes are not redrawn each time print(f"Drawing ax for {data_id}") ax = fig.add_subplot() ax = plot_data(*datas[data_id], y_label=data_id, ax=ax) return ax for data_state in states: axes = [get_data_ax(data_id) for data_id in data_state] update_figure_with_axes(fig, axes) plt.pause(1) def plot_data(x, y, *, y_label, ax=None, marker_sym=None, color_idx=0): if ax is None: # fig, ax = plt.subplots() # New figure # ax = plt.gca() # Current axis ax = plt.gcf().add_subplot() # New axis on current figure ax.plot(x, y) ax.set_ylabel(y_label) # Use `marker_sym` and `color_idx` return ax def update_figure_with_axes(fig, axes): """Every ax in `axes` should have already been plotted on `fig`""" # Remove all previous axes, add relevant ones later for ax in fig.axes: fig.delaxes(ax) # Create a GridSpec for the axes to be plot, # with at least 3 rows so plots are not too stretched gs = GridSpec(max(3, len(axes)), 1, hspace=0.4) # Add each ax one by one for ax, sgs in zip(axes, gs): ax.set_subplotspec(sgs) # Place ax at the right location ax.xaxis.set_major_formatter(NULL_FORMATTER) # Remove x ticks labels fig.add_subplot(ax) # Add back the x ticks labels for the bottom ax axes[-1].xaxis.set_major_formatter(FUNC_FORMATTER) # Share all axes with the bottom ax axes[-1].get_shared_x_axes().join(*axes) axes[-1].autoscale() def generate_data(num_datas): """ Generate `num_datas` random data, with 100 to 200 points, starting somewhere in between 100 and 150 on the x axis. """ return { f"data{data_idx:02d}": ( np.arange( start := np.random.randint(100, 150), start + (num_points := np.random.randint(100, 200)), ), # x np.random.randn(num_points), # y ) for data_idx in range(1, num_datas + 1) } def generate_states(keys, num_states, max_key_per_state): """Generate `num_states` draws of `keys`, in between 1 and `max_key_per_state`""" return tuple( np.random.choice( keys, replace=False, size=np.random.randint(1, max_key_per_state + 1), ) for _ in range(num_states) ) if __name__ == "__main__": main()