2

Background


I am trying to show three variables on a single plot. I have connected the three points using lines of different colours based on some other variables. This is shown here figure here


Problem


What I want to do is to have a different scale on the negative x-axis. This would help me in providing positive x_ticks, different axis label and also clear and uncluttered representation of the lines on left side of the image


Question


  • How to have a different positive x-axis starting from 0 towards negative direction?
  • Have xticks based on data plotted in that direction
  • Have a separate xlabel for this new axis

Additional information


I have checked other questions regarding inclusion of multiple axes e.g. this and this. However, these questions did not serve the purpose.

Code Used

font_size = 20
plt.rcParams.update({'font.size': font_size})

fig = plt.figure()
ax = fig.add_subplot(111)
#read my_data from file or create it

for case in my_data:

    #Iterating over my_data

    if condition1 == True:
        local_linestyle = '-'
        local_color = 'r'
        local_line_alpha = 0.6
    elif condition2 == 1:
        local_linestyle = '-'
        local_color = 'b'
        local_line_alpha = 0.6
    else:
        local_linestyle = '--'
        local_color = 'g'
        local_line_alpha = 0.6

    datapoint = [case[0], case[1], case[2]]

    plt.plot(datapoint[0], 0, color=local_color)
    plt.plot(-datapoint[2], 0, color=local_color)
    plt.plot(0, datapoint[1], color=local_color)
    plt.plot([datapoint[0], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
    plt.plot([-datapoint[2], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
plt.show()
exit()
Neeraj Hanumante
  • 1,575
  • 2
  • 18
  • 37

3 Answers3

2

You can define a custom scale, where values below zero are scaled differently than those above zero.

import numpy as np
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import FuncFormatter

class AsymScale(mscale.ScaleBase):
    name = 'asym'

    def __init__(self, axis, **kwargs):
        mscale.ScaleBase.__init__(self)
        self.a = kwargs.get("a", 1)

    def get_transform(self):
        return self.AsymTrans(self.a)

    def set_default_locators_and_formatters(self, axis):
        # possibly, set a different locator and formatter here.
        fmt = lambda x,pos: "{}".format(np.abs(x))
        axis.set_major_formatter(FuncFormatter(fmt))

    class AsymTrans(mtransforms.Transform):
        input_dims = 1
        output_dims = 1
        is_separable = True

        def __init__(self, a):
            mtransforms.Transform.__init__(self)
            self.a = a

        def transform_non_affine(self, x):
            return (x >= 0)*x + (x < 0)*x*self.a

        def inverted(self):
            return AsymScale.InvertedAsymTrans(self.a)

    class InvertedAsymTrans(AsymTrans):

        def transform_non_affine(self, x):
            return (x >= 0)*x + (x < 0)*x/self.a
        def inverted(self):
            return AsymScale.AsymTrans(self.a)

Using this you would provide a scale parameter a that scales the negative part of the axes.

# Now that the Scale class has been defined, it must be registered so
# that ``matplotlib`` can find it.
mscale.register_scale(AsymScale)

import matplotlib.pyplot as plt
fig, ax = plt.subplots()

ax.plot([-2, 0, 5], [0,1,0])
ax.set_xscale("asym", a=2)

ax.annotate("negative axis", xy=(.25,0), xytext=(0,-30), 
            xycoords = "axes fraction", textcoords="offset points", ha="center")
ax.annotate("positive axis", xy=(.75,0), xytext=(0,-30), 
            xycoords = "axes fraction", textcoords="offset points", ha="center")
plt.show()

enter image description here

The question is not very clear about what xticks and labels are desired, so I left that out for now.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
1

Here's how to get what you want. This solution uses two twined axes object to get different scaling to the left and right of the origin, and then hides all the evidence:

import matplotlib.pyplot as plt
import matplotlib as mpl
from numbers import Number

tickkwargs = {m+k:False for k in ('bottom','top','left','right') for m in ('','label')}

p = np.zeros((10, 3, 2))
p[:,0,0] -= np.arange(10)*.1 + .5
p[:,1,1] += np.repeat(np.arange(5), 2)*.1 + .3
p[:,2,0] += np.arange(10)*.5 + 2

fig = plt.figure(figsize=(8,6))
host = fig.add_subplot(111)
par = host.twiny()

host.set_xlim(-6, 6)
par.set_xlim(-1, 1)

for ps in p:
    # mask the points with negative x values
    ppos = ps[ps[:,0] >= 0].T
    host.plot(*ppos)

    # mask the points with positive x values
    pneg = ps[ps[:,0] <= 0].T
    par.plot(*pneg)

# hide all possible ticks/notation text that could be set by the second x axis
par.tick_params(axis="both", **tickkwargs)
par.xaxis.get_offset_text().set_visible(False)

# fix the x tick labels so they're all positive
host.set_xticklabels(np.abs(host.get_xticks()))

fig.show()

Output:

enter image description here

Here's what the set of points p I used in the code above look like when plotted normally:

fig = plt.figure(figsize=(8,6))
ax = fig.gca()
for ps in p:
    ax.plot(*ps.T)
fig.show()

Output:

enter image description here

tel
  • 13,005
  • 2
  • 44
  • 62
  • Could you please help in setting up x-axis labels. One in positive direction and one in negative direction e.g. say 'foo' in x positive and 'zoo' in x negative. – Neeraj Hanumante Dec 10 '18 at 12:03
0

The method of deriving a class of mscale.ScaleBase as shown in other answers may be too complicated for your purpose. You can pass two scale transform functions to set_xscale or set_yscale, something like the following.

def get_scale(a=1):  # a is the scale of your negative axis
    def forward(x):
        x = (x >= 0) * x + (x < 0) * x * a
        return x

    def inverse(x):
        x = (x >= 0) * x + (x < 0) * x / a
        return x

    return forward, inverse

fig, ax = plt.subplots()

forward, inverse = get_scale(a=3)
ax.set_xscale('function', functions=(forward, inverse))  # this is for setting x axis

# do plotting

More examples can be found in this doc.

Youwei Liang
  • 361
  • 4
  • 9