diff --git a/README.md b/README.md index 6b5b8ab29ed9b84ff0a1b0637a6e9d588de82a70..2613e7230d8aa558129442f34347fb1daa266d41 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,6 @@ This project consists of Jupyter Notebook definitions used by RWTH Aachen Univer - Create conda environment `conda env create -f environment.yml` - Activate environment `conda activate rwthlab` - Install Jupyterlab extensions with `jupyter labextension install @jupyter-widgets/jupyterlab-manager jupyter-matplotlib jupyterlab-rwth` - -For developers: Install `rwth_nb` with `python ./setup.sh develop` +- Install `rwth_nb`: + - with `pip install git+https://git.rwth-aachen.de/jupyter/rwth-nb.git` or + - with `python ./setup.sh develop` (for developers) diff --git a/rwth_nb/plots/mpl_decorations.py b/rwth_nb/plots/mpl_decorations.py index 1fe4bfafe668051337b02d1fe55d3425594466bb..73ef80f62bccb8f9e2323174012c24b5678e4e3a 100644 --- a/rwth_nb/plots/mpl_decorations.py +++ b/rwth_nb/plots/mpl_decorations.py @@ -57,56 +57,42 @@ def axis(ax): ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') - def on_xlims_change(axes=[]): + def on_xlims_change(ax): # update x spine position to be always at zero, left or right according to set limits # update y-label x-position and horizontal alignment - if ax.get_xscale() != 'log': # most likely linear scale - is_lim_all_negative = (np.array(ax.get_xlim()) < 0).all() - is_lim_all_positive = (np.array(ax.get_xlim()) > 0).all() - if is_lim_all_negative: # all limits negative - left_spine_pos = ('axes', 1) - ylabel_xpos = 1 - ylabel_halignment = 'right' - elif is_lim_all_positive: # all limits positive - left_spine_pos = ('axes', 0) # spine at the left - ylabel_xpos = 0 - ylabel_halignment = 'left' - else: # zero is in plot - left_spine_pos = 'zero' # spine at zero ([0, 0]) - xmin = ax.get_xlim()[0] - xmax = ax.get_xlim()[1] - ylabel_xpos = np.abs(xmin) / (xmax + np.abs(xmin)) - ylabel_halignment = 'left' - else: # logarithmic scale - left_spine_pos = ('axes', 0) + if (np.array(ax.get_xlim()) < 0).all(): # all limits negative + left_spine_pos = ('axes', 1) + ylabel_xpos = 1 + ylabel_halignment = 'right' + elif (np.array(ax.get_xlim()) > 0).all(): # all limits positive + left_spine_pos = ('axes', 0) # spine at the left ylabel_xpos = 0 ylabel_halignment = 'left' + else: # zero is in plot + left_spine_pos = 'zero' # spine at zero ([0, 0]) + xmin = ax.get_xlim()[0] + xmax = ax.get_xlim()[1] + ylabel_xpos = np.abs(xmin) / (np.abs(xmax) + np.abs(xmin)) + ylabel_halignment = 'left' ax.spines['left'].set_position(left_spine_pos) ax.yaxis.set_label_coords(ylabel_xpos, 1) ax.yaxis.label.set_horizontalalignment(ylabel_halignment) - def on_ylims_change(axes=[]): + def on_ylims_change(ax): # update y spine position to be always at zero, top or bottom according to set limits # update x-label y-position - if ax.get_yscale() != 'log': # most likely linear scale - is_lim_all_negative = (np.array(ax.get_ylim()) < 0).all() - is_lim_all_positive = (np.array(ax.get_ylim()) > 0).all() - if is_lim_all_negative: # all limits negative - bottom_spine_pos = ('axes', 1) # spine at the top - xlabel_ypos = 1 - - elif is_lim_all_positive: # all limits positive - bottom_spine_pos = ('axes', 0) # spine at the bottom - xlabel_ypos = 0 - else: # zero is in plot - bottom_spine_pos = 'zero' # spine at zero ([0, 0]) - ymin = ax.get_ylim()[0] - ymax = ax.get_ylim()[1] - xlabel_ypos = np.abs(ymin) / (ymax + np.abs(ymin)) - else: # logarithmic scale - bottom_spine_pos = ('axes', 0) + if (np.array(ax.get_ylim()) < 0).all(): # all limits negative + bottom_spine_pos = ('axes', 1) # spine at the top + xlabel_ypos = 1 + elif (np.array(ax.get_ylim()) > 0).all(): # all limits positive + bottom_spine_pos = ('axes', 0) # spine at the bottom xlabel_ypos = 0 + else: # zero is in plot + bottom_spine_pos = 'zero' # spine at zero ([0, 0]) + ymin = ax.get_ylim()[0] + ymax = ax.get_ylim()[1] + xlabel_ypos = np.abs(ymin) / (np.abs(ymax) + np.abs(ymin)) ax.spines['bottom'].set_position(bottom_spine_pos) ax.xaxis.set_label_coords(1, xlabel_ypos) @@ -114,12 +100,12 @@ def axis(ax): ax.callbacks.connect('xlim_changed', on_xlims_change) ax.callbacks.connect('ylim_changed', on_ylims_change) - on_xlims_change() + on_xlims_change(ax) ax.xaxis.label.set_verticalalignment('bottom') ax.xaxis.label.set_horizontalalignment('right') ax.yaxis.label.set_rotation(0) - on_ylims_change() + on_ylims_change(ax) ax.yaxis.label.set_verticalalignment('top') ax.yaxis.label.set_horizontalalignment('left') @@ -127,6 +113,86 @@ def axis(ax): ax.yaxis.label.set_fontsize(12) +def twinx(ax, visible_spine='left'): + """ + Create a twin Axes sharing the x-axis. + + Parameters + ---------- + ax: matplotlib.axes.Axes + Existing Axes + visible_spine: {'left', 'right'}, str, optional + Position of the only visible axis spine + + Returns + ------- + ax_twin: matplotlib.axes.Axes + The newly created Axes instance + + See also + -------- + matplotlib.axes.Axes.twinx + twiny: Create a twin Axes sharing the y-axis. + + """ + if visible_spine in ['left', 'right']: + # remove visible spine from hidden spine list + hidden_spines = ['top', 'bottom', 'left', 'right'] + hidden_spines.remove(visible_spine) + + # create twiny and hide spines + ax_twin = ax.twiny() + for pos in hidden_spines: + ax_twin.spines[pos].set_color('none') + + # set label position according to spine position (left/right, top) + ax_twin.yaxis.set_label_coords(visible_spine == 'right', 1) + + return ax_twin + else: + # invalid keyword + raise ValueError('Twin x-axis location must be either "left" or "right"') + + +def twiny(ax, visible_spine='top'): + """ + Create a twin Axes sharing the y-axis. + + Parameters + ---------- + ax: matplotlib.axes.Axes + Existing Axes + visible_spine: {'top', 'bottom'}, str, optional + Position of the only visible axis spine + + Returns + ------- + ax_twin: matplotlib.axes.Axes + The newly created Axes instance + + See also + -------- + matplotlib.axes.Axes.twiny + twinx: Create a twin Axes sharing the x-axis. + """ + if visible_spine in ['top', 'bottom']: + # remove visible spine from hidden spine list + hidden_spines = ['top', 'bottom', 'left', 'right'] + hidden_spines.remove(visible_spine) + + # create twiny and hide spines + ax_twin = ax.twiny() + for pos in hidden_spines: + ax_twin.spines[pos].set_color('none') + + # set label position according to spine position (right, bottom/top) + ax_twin.xaxis.set_label_coords(1, visible_spine == 'top') + + return ax_twin + else: + # invalid keyword + raise ValueError('Twin y-axis location must be either "top" or "bottom"') + def annotate_xtick(ax, txt, x, y=0, col='black', fs=12): """