marcharper / python-ternary

:small_red_triangle: Ternary plotting library for python with matplotlib
MIT License
734 stars 157 forks source link

Cut off top of triangle? #192

Open Peacemoose10 opened 2 years ago

Peacemoose10 commented 2 years ago

Hello!

First, thank you so much for making this package - it's so helpful! I am a geologist, and we often use ternary diagrams with the top cut off, such as the plot below (purely an example I got from the internet).

image

I am struggling to replicate this with your packages. I am using subplots, so I can control the height of the axes using ax.set_ylim, but this just stretches the ternary (example below), and leaves some of the ternary tick marks stretched out above the plot. Is there a way to plot only part of a ternary axis?

strechingproblemexample

Basically, I guess I want to do something like the last part of the solution to #190 but without the free floating ticks.

Thank you! M

bmondal94 commented 2 years ago
import ternary
import numpy as np

i = [1, 0.7, 0.5, 0.3, 0, 0.7, 0.5, 0.3, 0.4, 0.3, 0.3, 0]
j = [0, 0.3, 0.5, 0.7, 1, 0, 0 , 0, 0.3, 0.3, 0.4, 0.7]
k = [0, 0, 0, 0, 0, 0.3, 0.5, 0.7, 0.3, 0.4, 0.3, 0.3]
Z = np.linspace(0,1,12)

data_dictionary2 = {(i[a], j[a], k[a]) for a in range(len(Z))}
fontsize = 18
#------------------------ Original plot ---------------------------------------
offset = 0.25

fig, tax = ternary.figure(scale=1)
tax.scatter(data_dictionary2)
tax.ticks(axis='lbr', multiple=.1, linewidth=1, offset=0.025, tick_formats="%.1f", fontsize=fontsize)
tax.get_axes().axis('off')
tax.clear_matplotlib_ticks()

tax.boundary(linewidth=2.0)
tax.left_axis_label("Left label $\\alpha^2$", fontsize=fontsize, offset=offset)
tax.right_axis_label("Right label $\\beta^2$", fontsize=fontsize, offset=offset)
tax.bottom_axis_label("Bottom label $\\Gamma - \\Omega$", fontsize=fontsize, offset=offset)

tax.show()

#==============================================================================
#----------------------- After cut --------------------------------------------
ycut = 0.5 # y-axis cut in ternary scale at 0.5
ycut_cart = np.sqrt(3)/2 * ycut # y-axis cut in cartesian coordinate at sqrt(3)/2 * ycut

fig, tax = ternary.figure(scale=1)
tax.scatter(data_dictionary2)
tax.ticks(axis='lbr', multiple=.1, linewidth=1, offset=0.025, tick_formats="%.1f",fontsize=fontsize)
tax.get_axes().axis('off')
tax.clear_matplotlib_ticks()
tax.boundary(linewidth=2.0)

ax = tax.get_axes()
# Note: ymin is not 0.0 but there is an extra lower limit (-0.1) in y-axis to fit 
# the canvas perfectly. Check resize_drawing_canvas() in plotting.py script.
# Calculation: Ternary y-scale maximum 1 == Cartesian y-scale sqrt(3)/2
#              Ternary y-scale maximum ycut == Cartesian y-scale sqrt(3)/2 * ycut
ax.set_ylim(ymax=ycut_cart) # this ymax should be in cartesian coordinate scale.

# Need to increase the offset for left and right labels. Because _redraw_labels()
# will redraw the labels according to the new limits. As in this example the
# bottom ylim was not changed, so one doesn't need to change the bottom_axis_label()
# offset value. 
# Note: tax.show() already calls the tax._redraw_labels().
tax.left_axis_label("Left label $\\alpha^2$", fontsize=fontsize, offset=offset+0.05)
tax.right_axis_label("Right label $\\beta^2$", fontsize=fontsize, offset=offset+0.05)
tax.bottom_axis_label("Bottom label $\\Gamma - \\Omega$", fontsize=fontsize, offset=offset)

# Resize the figure aspect ratio to avoid the streching.
import matplotlib
# height = ycut + 0.1 # 0.1 is the extra ymin used to fit canvas in vertical direction
# width = xlimit(=1) + 2*0.05 # In horizontal axis extra 0.05 was used in both left 
# right to fit the canvas. Check resize_drawing_canvas() in ternary/plotting.py script.
# ratio = height/width
fig_aspect_ratio = (ycut+0.1)/(1+0.1)
w, h = matplotlib.figure.figaspect(fig_aspect_ratio) 
fig.set_size_inches(w, h)

#tax._redraw_labels()
tax.show()
# fig.savefig('/home/test.png',format='png',dpi=300)

#==============================================================================
#---------------------- Alternative way to set figure aspect ratio ------------
import matplotlib
# Resize the figure aspect ratio to avoid the streching.
w, h = matplotlib.figure.figaspect(fig_aspect_ratio)
fig, ax = matplotlib.pyplot.subplots(figsize=(w,h))
tax = ternary.TernaryAxesSubplot(ax=ax, scale = 1)
tax.scatter(data_dictionary2)
tax.ticks(axis='lbr', multiple=.1, linewidth=1, offset=0.025, tick_formats="%.1f",fontsize=fontsize)
tax.get_axes().axis('off')
tax.clear_matplotlib_ticks()
tax.boundary(linewidth=2.0)

ax.set_ylim(ymax=ycut_cart) 

tax.left_axis_label("Left label $\\alpha^2$", fontsize=fontsize, offset=offset+0.05)
tax.right_axis_label("Right label $\\beta^2$", fontsize=fontsize, offset=offset+0.05)
tax.bottom_axis_label("Bottom label $\\Gamma - \\Omega$", fontsize=fontsize, offset=offset)

tax.show()
Peacemoose10 commented 2 years ago

Thank you very much! The last example using subplots is exactly what I needed!

Peacemoose10 commented 2 years ago

Actually, so sorry - I am still struggling to get the numbers above the cut off to disappear. I want to make a figure that has two rows, but when I do that, the ternary numbers above the cutoff bleed into the next plot. I can't figure out how to make them disappear or plot under the top row (I've tried using zorder but that doesn't seem to make a difference). Thank you for your help!

Ternary_AllAnalysisSites_SeparatePlots_quads

bmondal94 commented 2 years ago

Quick and dirty: Add clip_on=True in the ax.text calls inside ternary/lines.py script. ax.text(x, y, s, horizontalalignment="center", color=axes_colors['l'], fontsize=fontsize,clip_on=True)

bmondal94 commented 2 years ago

A better solution:

import ternary
import numpy as np
from matplotlib import pyplot, gridspec

#-------------------- Function definition for manual tick settings ------------
def ManualTicks(tax, limits):
    '''
    This function creates the ticks. Similar to set_custom_ticks() function in
    ternary/ternary_axes_subplot.py .

    Parameters
    ----------
    tax : axis
        Ternary axis.
    limits : dictionary
        Dictionary containing limits. b, l, r == bottom, left, right

    Returns
    -------
    None.

    '''

    for k in ['b', 'r', 'l']:
        minimum, maximum, step = limits[k]
        myticks = np.arange(minimum, maximum, step)
        mylocations = np.copy(myticks) - min(myticks) if k=='l' else myticks 
        tax.ticks(ticks=list(myticks), locations=list(mylocations), axis=k,
                  linewidth=1, offset=0.025, fontsize=fontsize)

#--------------------------Create random data----------------------------------
pointsx = np.random.randint(1,50,size=100)
pointsy = np.random.randint(1,50,size=100)
data_dictionary = {(pointsx[a], pointsy[a]) for a in range(len(pointsx))}

fontsize = 18
offset = 0.25

# If you need to cut the ylimit at different positions
# for different subplots you can make a array of cut limits here and
# call accordingly later.
ycut_cart = np.sqrt(3)/2* 50  
limits = {'b':(10,110,10), 'r':(0,60,10), 'l':(50,110,10)} # limits = {which_axis:(minimum_limit, maximum_limit, step_size)}

#-------------------------- Plotting -----------------------------------------
pyplot.figure(figsize=(12,8))
nrows = 2; ncols = 2
gs = gridspec.GridSpec(nrows,ncols)
pyplot.subplots_adjust(wspace=0.3,hspace=0.5)

LeftAxisLabels = ['$\\alpha_1^2$', '$\\alpha_2^2$', '$\\alpha_3^2$', '$\\alpha_4^2$']
RightAxisLabels = ['$\\beta_1^2$','$\\beta_2^2$','$\\beta_3^2$','$\\beta_4^2$']
BottomAxisLabels = ['$\\Gamma_1$','$\\Gamma_2$','$\\Gamma_3$','$\\Gamma_4$']

n = 0
for i in range(nrows):
    for j in range(ncols):       
        ax = pyplot.subplot(gs[i, j])
        figure, tax = ternary.figure(ax=ax, scale = 100)
        tax.scatter(data_dictionary)

        tax.boundary(linewidth=1.0)
        tax.set_title(f"Scatter Plot {i,j}", fontsize=fontsize, pad=30)
        tax.left_axis_label(LeftAxisLabels[n], fontsize=fontsize, offset=0.28)
        tax.right_axis_label(RightAxisLabels[n], fontsize=fontsize, offset=0.3)
        tax.bottom_axis_label(BottomAxisLabels[n], fontsize=fontsize, offset=0.25)

        tax._redraw_labels()
        tax.get_axes().set_ylim(ymax=ycut_cart) 
        tax.get_axes().axis('off')
        tax.clear_matplotlib_ticks()

        # Lets create the ticks manually       
        ManualTicks(tax, limits)

        n+=1