Search code examples
pythonmatplotlibaxis-labels

Matplotlib: Hysteresis loop using Mirrored or Split x axis


In an experiment, a load cell advances in equal increments of distance with time, compresses a sample; stops when a specified distance from the start point is reached; then retracts in equal increments of distance with time back to the starting position.

A plot of pressure (load cell reading) on the y axis against pressure on the x axis produces a familiar hysteresis loop. A plot of pressure (load cell reading) on the y axis against time on the x axis produces an assymetric peak with the maximum pressure in the centre, corresponding to the maximum advancement point of the sensor.

Instead of the above, I'd like to plot pressure on the y axis against distance on the x axis, with the additional constraint that the x axis is labelled starting at 0, with maximum pressure at the middle of the x axis, and 0 again at the right hand end of the x-axis. In other words, the curve will be identical in shape to the plot of pressure v time, but will be of pressure v distance, where the left half of the plot indicates the distance of the probe from its starting position during advancement; and the right half of the plot indicates distance of the probe from its starting position during retraction.

My actual datasets contain thousands of rows of data but by way of illustration, a minimal dummy dataset would look something like the following, where the 3 columns correspond to Time, Distance of probe from origin, and Pressure measured by probe respectively:

[
[0,0,0],
[1,2,10],
[2,4,30],
[3,6,60],
[4,4,35],
[5,2,15],
[6,0,0]
]

I can't work out how to get MatPlotlib to construct the x-axis so that the range goes from 0 to a maximum, then back to 0 again. I'd be grateful for advice on how to achieve this plot in the most simple and elegant way. Many thanks.


Solution

  • Update: Have found a workaround to the problem of rounding ticks to the nearest integer by using the np.around function which rounds decimals to the nearest even value, to a specified number of decimal places (default = 0): e.g. 1.5 and 2.5 round to 2.0, -0.5 and 0.5 round to 0.0, etc. More info here: https://docs.scipy.org/doc/numpy1.10.4/reference/generated/numpy.around.html

    So berna1111's code becomes:

    import numpy as np
    import matplotlib.pyplot as plt
    
    # Time, Distance, Pressure
    data = [[0, 0, 0],
            [1, 1.9, 10], # Dummy data including decimals to demonstrate rounding
            [2, 4.1, 30],
            [3, 6.1, 60],
            [4, 3.9, 35],
            [5, 1.9, 15],
            [6, -0.2, 0]]
    
    # convert to array to allow indexing like [i, j]
    data = np.array(data)
    
    fig = plt.figure()
    ax = fig.add_subplot(111)
    
    max_ticks = 10
    skip = (data.shape[0] / max_ticks) + 1
    ax.plot(data[:, 0], data[:, 2])  # Pressure(time)
    ax.set_xticks(data[::skip, 0])
    ax.set_xticklabels(np.absolute(np.around((data[::skip, 1]))))  # Pressure(Distance(time)); rounded to nearest integer
    ax.set_ylabel('Pressure [Pa?]')
    ax.set_xlabel('Distance [m?]')
    fig.show()
    

    According to the numpy documentation, np.around should round the final value of -0.2 for Distance to '0.0'; however it seems to round to '-0.0' instead. Not sure why this occurs, but since all my xticklabels in this particular case need to be positive integers or zero, I can correct this behaviour by using the np.absolute function as shown above. Everything now seems to work OK for my requirements, but if I'm missing something, or there's a better solution, please let me know.