Search code examples
pythonmatplotlibcolorsscatter-plot

Assign line a color by its angle in matplotlib


I'm looking for a way to assign color to line plots in matplotlib in a way that's responsive to the line's angle. This is my current code:

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
horz = [[0.5,0.6,0.8],[0.1,0.8,0.9],[0.2,0.5,0.9]] 
vert = [[0.1,0.2,0.3],[0.05,0.1,0.15],[0.2,0.3,0.35]]

f = plt.figure(figsize=(6,6))
ax = plt.axes()

for column in range(0,len(horz)):
    x = np.array(horz[column])
    y = np.array(vert[column])
            
    #LINEAR TRENDLINE
    z = np.polyfit(horz[column], vert[column], 1)
    p = np.poly1d(z)
    ax.plot(horz[column],p(horz[column]),"-")
    plt.arrow(x=horz[column][-2],y=p(horz[column])[-2],dx=(horz[column][-1]-horz[column][-2]),dy=(p(horz[column])[-1]-p(horz[column])[-2]), shape='full', lw=.01,
              length_includes_head=True, head_width=.012, head_length=0.02, head_starts_at_zero=False, overhang = 0.5)
            
    #FIG SETTINGS
    plt.xlim([0, 1])
    plt.ylim([0.1,0.5])
    ax.set_title('Title',
             fontsize = 14)

As it is now, the colors are randomly assigned

The idea here would be that if the line is at 0 degrees, it would be at one end of a given gradient, and if it were at 90 degrees, at the other end. Additionally, I'd like the line length to be taken as the intensity of the color. So if the line is short, it'd be closer to white, and if the line is long, it'd be closer to the raw color from the gradient.


Solution

  • Managed to solve it myself. Used pretty simple formulas for calculating the lines' slopes and distances and then used these as input for the color mapping and alpha transparency attribute.

    import geopandas as gpd
    import pandas as pd
    import matplotlib.pyplot as plt
    from matplotlib import cm
    import matplotlib.colors as colors
    import numpy as np
    
    %matplotlib inline
    
    #Data
    horz = [[0.5,0.6,0.8],[0.1,0.3,0.4],[0.2,0.5,0.9],[0.9,0.95,0.95]] 
    vert = [[0.1,0.2,0.45],[0.05,0.1,0.15],[0.2,0.3,0.35],[0.1,0.3,0.5]]
                
    
    #Slope calculation
    def slopee(x1,y1,x2,y2):
            x = (y2 - y1) / (x2 - x1)
            return x
    
    #Color set up
    cmap = plt.cm.coolwarm_r
    #0 means a horizontal line, 1 means a line at 45 degrees, infinite means a vertical line (2 is vertical enough)
    cNorm  = colors.Normalize(vmin=0, vmax=2)
    scalarMap = cm.ScalarMappable(norm=cNorm,cmap=cmap)
    
    #Fig settings
    f = plt.figure(figsize=(6,6))
    ax = plt.axes()
    
    
    for column in range(0,len(horz)):
        x = np.array(horz[column])
        y = np.array(vert[column])
        
        #LINEAR TRENDLINE
        # 1   LINEAR
        # >=2 POLINOMIAL
        z = np.polyfit(horz[column], vert[column], 1)
        p = np.poly1d(z)
        
        #Distance calc formula
        def calculateDistance(x1,y1,x2,y2):
            dist = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
            return dist
        
        #Set up max an min distances
        maxdist = calculateDistance(0,0,0,0.9)
        mindist = calculateDistance(0,0,0,0)
        
        #Calculate line slope
        slope = slopee(horz[column][0],p(horz[column])[0],horz[column][-1],p(horz[column])[-1])
        
        #Not interested in any slopes going "down"
        if slope >=0:
            #Map colors based on slope (0-2)
            colorVal = scalarMap.to_rgba(slope)
            #Map transparency based on distance
            transparency = (calculateDistance(horz[column][0],p(horz[column])[0],horz[column][-1],p(horz[column])[-1])-mindist)/(maxdist-mindist)
            #Set up minimun transparency to be 50% instead of 0%
            transparency = (0.5*transparency) + 0.5
            #The actual arrow plot
            plt.arrow(x=horz[column][0],y=p(horz[column])[0],dx=(horz[column][-1]-horz[column][0]),dy=(p(horz[column])[-1]-p(horz[column])[0]), shape='full',length_includes_head=True, head_starts_at_zero=False, lw=.5, head_width=.011, head_length=0.01, overhang = 0.5, color=colorVal,alpha=transparency)
    
        #FIG SETTINGS
        plt.xlim([0, 1])
        plt.ylim([0,0.5])
        ax.set_title('Title',fontsize = 14)
    

    Color hue and intensity vary with slope and length