1

This is my current script:

#!/usr/bin/env python3

import math
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

"""
Setup for a typical explanatory-style illustration style graph.
"""

h = 2
x = np.linspace(-np.pi, np.pi, 100)
y = 2 * np.sin(x)
rc = {
    # Tick in the middle of the axis line.
    'xtick.direction' : 'inout',
    'ytick.direction' : 'inout',

    # Bold is easier to read when we have few ticks.
    'font.weight': 'bold',
    'xtick.labelbottom': False,
    'xtick.labeltop': True,
}
with plt.rc_context(rc):
    fig, ax = plt.subplots()
    ax.plot(x, y)
    ax.set_title(
        '2 sin(x), not $\\sqrt{2\\pi}$',
        # TODO make LaTeX part bold?
        # https://stackoverflow.com/questions/14324477/bold-font-weight-for-latex-axes-label-in-matplotlib
        fontweight='bold',
        # Too close otherwise.
        # https://stackoverflow.com/questions/16419670/increase-distance-between-title-and-plot-in-matplolib/56738085
        pad=20
    )

    # Custom visible plot area.
    # ax.set_xlim(-3, 3)
    ax.set_ylim(-2.5, 2.5)

    # Axes
    # Axes on center:
    # https://stackoverflow.com/questions/31556446/how-to-draw-axis-in-the-middle-of-the-figure
    ax.spines['left'].set_position('zero')
    ax.spines['right'].set_visible(False)
    ax.spines['bottom'].set_position('zero')
    ax.spines['top'].set_visible(False)
    # Axes with arrow:
    # https://stackoverflow.com/questions/33737736/matplotlib-axis-arrow-tip
    ax.plot(1, 0, ls="", marker=">", ms=10, color="k",
            transform=ax.get_yaxis_transform(), clip_on=False)
    ax.plot(0, 1, ls="", marker="^", ms=10, color="k",
            transform=ax.get_xaxis_transform(), clip_on=False)

    # Ticks
    ax.xaxis.set_ticks_position('bottom')
    ax.yaxis.set_ticks_position('left')
    # Make ticks a bit longer.
    ax.tick_params(width=1, length=10)
    # Select tick positions
    # https://stackoverflow.com/questions/12608788/changing-the-tick-frequency-on-x-or-y-axis-in-matplotlib
    xticks = np.arange(math.ceil(min(x)),     math.floor(max(x)) + 1, 1)
    yticks = np.arange(math.ceil(min(y)) - 1, math.floor(max(y)) + 2, 1)
    # Remove 0.
    xticks = np.setdiff1d(xticks, [0])
    yticks = np.setdiff1d(yticks, [0])
    ax.xaxis.set_ticks(xticks)
    ax.yaxis.set_ticks(yticks)
    # Another approach. But because I want to be able to remove the 0,
    # anyways, I just explicitly give all ticks instead.
    # ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(1.0))
    # ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(1.0))

    # Annotations.
    ax.plot([0, np.pi/2], [h, h], '--r')
    ax.plot([np.pi/2, np.pi/2], [h, 0], '--r')
    ax.plot(np.pi/2, h, marker='o', linewidth=2, markersize=10,
        markerfacecolor='w', markeredgewidth=1.5, markeredgecolor='black')

plt.savefig(
    'main.png',
    format='png',
    bbox_inches='tight'
)
plt.clf()

And this is the output:

enter image description here

And this is what I want (hacked with GIMP), notice how the negative tick labels are on a different side of the axes now.

enter image description here

I tried adding:

    for tick in ax.xaxis.get_majorticklabels():
        tick.set_verticalalignment("bottom")

as shown in answers to: How to move a tick's label in matplotlib? but that does not move the tick labels up enough, and makes the labels show on top of the axes instead.

Tested on matplotlib 3.2.2.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985

1 Answers1

2

The following code will adjust the vertical alignment of the ticks depending one whether they are at a negative or positive x-value. However that's not enough because the labels are actually anchored at the bottom of the tick line. I'm therefore adjusting their y-position a little bit, but you have to play with the value to get the desired output

# adjust the xticks so that they are on top when x<0 and on the bottom when x≥0
ax.spines['top'].set_visible(True)
ax.spines['top'].set_position('zero')
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_position('zero')
ax.xaxis.set_tick_params(which='both', top=True, labeltop=True,
                             bottom=True, labelbottom=True)
fig.canvas.draw()
for tick in ax.xaxis.get_major_ticks():
    print(tick.get_loc())
    if tick.get_loc()<0:
        tick.tick1line.set_visible(False)
        tick.label1.set_visible(False)
    else:
        tick.tick2line.set_visible(False)
        tick.label2.set_visible(False)

enter image description here

full code:

import math
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

"""
Setup for a typical explanatory-style illustration style graph.
"""

h = 10
x = np.linspace(-np.pi, np.pi, 100)
y = h * np.sin(x)
rc = {
    # Tick in the middle of the axis line.
    'xtick.direction' : 'inout',
    'ytick.direction' : 'inout',

    # Bold is easier to read when we have few ticks.
    'font.weight': 'bold',
    'xtick.labelbottom': False,
    'xtick.labeltop': True,
}
with plt.rc_context(rc):
    fig, ax = plt.subplots()
    ax.plot(x, y)
    ax.set_title(
        '2 sin(x), not $\\sqrt{2\\pi}$',
        # TODO make LaTeX part bold?
        # https://stackoverflow.com/questions/14324477/bold-font-weight-for-latex-axes-label-in-matplotlib
        fontweight='bold',
        # Too close otherwise.
        # https://stackoverflow.com/questions/16419670/increase-distance-between-title-and-plot-in-matplolib/56738085
        pad=20
    )

    # Custom visible plot area.
    # ax.set_xlim(-3, 3)
    ax.set_ylim(-2.5, 2.5)

    # Axes
    # Axes on center:
    # https://stackoverflow.com/questions/31556446/how-to-draw-axis-in-the-middle-of-the-figure
    ax.spines['left'].set_position('zero')
    ax.spines['right'].set_visible(False)
    ax.spines['bottom'].set_position('zero')
    ax.spines['top'].set_visible(False)
    # Axes with arrow:
    # https://stackoverflow.com/questions/33737736/matplotlib-axis-arrow-tip
    ax.plot(1, 0, ls="", marker=">", ms=10, color="k",
            transform=ax.get_yaxis_transform(), clip_on=False)
    ax.plot(0, 1, ls="", marker="^", ms=10, color="k",
            transform=ax.get_xaxis_transform(), clip_on=False)

    # Ticks
    ax.xaxis.set_ticks_position('bottom')
    ax.yaxis.set_ticks_position('left')
    # Make ticks a bit longer.
    ax.tick_params(width=1, length=10)
    # Select tick positions
    # https://stackoverflow.com/questions/12608788/changing-the-tick-frequency-on-x-or-y-axis-in-matplotlib
    xticks = np.arange(math.ceil(min(x)),     math.floor(max(x)) + 1, 1)
    yticks = np.arange(math.ceil(min(y)) - 1, math.floor(max(y)) + 2, 1)
    # Remove 0.
    xticks = np.setdiff1d(xticks, [0])
    yticks = np.setdiff1d(yticks, [0])
    ax.xaxis.set_ticks(xticks)
    ax.yaxis.set_ticks(yticks)
    # Another approach. But because I want to be able to remove the 0,
    # anyways, I just explicitly give all ticks instead.
    # ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(1.0))
    # ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(1.0))
    
    for g,t in zip(ax.get_xticks(),ax.get_xticklabels()):
        if g<0:
            t.set_va('bottom')
        else:
            t.set_va('top')
        t.set_transform(ax.transData)
        t.set_position((g,0.15*-(g/abs(g))))

    # Annotations.
    ax.plot([0, np.pi/2], [h, h], '--r')
    ax.plot([np.pi/2, np.pi/2], [h, 0], '--r')
    ax.plot(np.pi/2, h, marker='o', linewidth=2, markersize=10,
        markerfacecolor='w', markeredgewidth=1.5, markeredgecolor='black')
    
    
    # adjust the xticks so that they are on top when x<0 and on the bottom when x≥0
    ax.spines['top'].set_visible(True)
    ax.spines['top'].set_position('zero')
    ax.spines['bottom'].set_visible(True)
    ax.spines['bottom'].set_position('zero')
    ax.xaxis.set_tick_params(which='both', top=True, labeltop=True,
                                 bottom=True, labelbottom=True)
    fig.canvas.draw()
    for tick in ax.xaxis.get_major_ticks():
        print(tick.get_loc())
        if tick.get_loc()<0:
            tick.tick1line.set_visible(False)
            tick.label1.set_visible(False)
        else:
            tick.tick2line.set_visible(False)
            tick.label2.set_visible(False)

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75