dxc
dxc

Reputation: 115

Shifted color of

Im trying to achieve this ideal graphical formatting of the plot: enter image description here

I successfully generated the graph, also somehow (plt.pcolor) overwrited the default colors of plot (something that maybe is overkill, but I cannot find other option), but I'm struggling that:

  1. Next color layer have offset to main grid

enter image description here

  1. I also have no idea how remove all tick (sticks) from top, left, down but keep labels
  2. And I would like to shift upper labels more down
  3. I also want to add 3 conditions for color. The blue can be also gradual blue based on first plot color

enter image description here

Here is functional code to generate the plot (on the middle picture):

import numpy as np
import pandas as pd
from datetime import datetime as dt
from datetime import timedelta
import matplotlib
import matplotlib.pyplot as plt
from calendar import monthrange
import matplotlib.colors as mcolors

def create_graph(all_rows, task_names, farmers):

    harvest = np.array(all_rows)
    fig, ax = plt.subplots()
    im = ax.imshow(harvest)

    # We want to show all ticks...
    ax.set_xticks(np.arange(len(farmers)))
    ax.set_yticks(np.arange(len(task_names)))
    # ... and label them with the respective list entries
    ax.set_xticklabels(farmers, fontsize=4)
    ax.set_yticklabels(task_names, fontsize=8)

    # Rotate the tick labels and set their alignment.
    plt.setp(ax.get_xticklabels(), rotation=0, ha="center", rotation_mode="anchor")
    ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)
    
    #Turn spines off and create white grid.
    for edge, spine in ax.spines.items():
        spine.set_visible(False)

    cmap, norm = mcolors.from_levels_and_colors([0, 1, 4], ['grey', 'green'])

    plt.pcolor(harvest, cmap=cmap, norm=norm, snap=True) 
    ax.set_xticks(np.arange(harvest.shape[1]+1)-.5, minor=True)
    ax.set_yticks(np.arange(harvest.shape[0]+1)-.5, minor=True)
    ax.grid(which="minor", color="w", linestyle='-', linewidth=1)
    
    # ax.tick_params(which="minor", bottom=False, left=False)
    # ax.set_title("Harvest of local farmers (in tons/year)")
    fig.tight_layout()
    plt.show()
    plt.close()


vegetables = ["cucumber", "tomato", "lettuce", "asparagus",
                  "potato", "wheat", "barley"]
farmers = [1, 2, 3, 4, 5, 6, 7]
    
harvest = np.array([[0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0],
                    [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0],
                    [1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0],
                    [0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0],
                    [0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0],
                    [1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1],
                    [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3]])
    
create_graph(harvest, vegetables, farmers)

I tried to look in matplotlib but, I'm not more wise than before.

Upvotes: 2

Views: 456

Answers (3)

dxc
dxc

Reputation: 115

This is the result graph made from both of your suggestion!

This is the result graph made from both of you!

What I'm struggling is the fine tune the colormap. From all colormaps I chose this one, but the lower values are just too bright. Is there any way how to trim the colormap so it will not start from that yellow but it will start in range I draw (red square)?

Upvotes: 0

JohanC
JohanC

Reputation: 80509

Some remarks:

  • To remove all tick marks: ax.tick_params(which='both', length=0). Note that you can call ax.tick_params many times with different parameters. The tick labels will automatically be set closer to the plot. Default it only operates on the major ticks, so which='both' makes it also operate on the minor ticks.
  • imshow has borders at the halves, which places the integer positions nicely in the center of each cell.
  • pcolor (and pcolormesh) suppose a grid of positions (default at the integers), so it doesn't align well with imshow.
  • When minor ticks overlap with major ticks, they are suppressed. So, simply setting them at multiples of 0.5 works in this case.
  • vmin and vmax tell which number corresponds to the lowest and which to the highest color of the colormap.
  • an over color can be set for all values higher than vmax. This value can be displayed in the colorbar via extend='max'. (Default, just the highest color is used for all values higher than vmax, and similarly the lowest color for the values below vmin). Note that if you use a standard colormap and want to use set_over, you need to make a copy of the colormap (because set_over changes the colormap in place and you would want other plots not to be affected).
  • The default colormap is called 'viridis'. Many others are possible.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.ticker import MultipleLocator

def create_graph(all_rows, task_names, farmers):
    harvest = np.array(all_rows)
    fig, ax = plt.subplots()

    # We want to show all ticks...
    ax.set_xticks(np.arange(len(farmers)))
    ax.set_yticks(np.arange(len(task_names)))
    # ... and label them with the respective list entries
    ax.set_xticklabels(farmers, fontsize=4)
    ax.set_yticklabels(task_names, fontsize=8)

    # Rotate the tick labels and set their alignment.
    plt.setp(ax.get_xticklabels(), rotation=0, ha="center", rotation_mode="anchor")
    ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)
    ax.tick_params(which='both', length=0) # hide tick marks
    ax.xaxis.set_minor_locator(MultipleLocator(0.5)) # minor ticks at halves
    ax.yaxis.set_minor_locator(MultipleLocator(0.5)) # minor ticks at halves

    # Turn spines off and create white grid.
    for edge, spine in ax.spines.items():
        spine.set_visible(False)

    cmap = mcolors.LinearSegmentedColormap.from_list("grey-blue", ['grey', 'steelblue'])
    cmap.set_over('green')

    im = ax.imshow(harvest, cmap=cmap, vmin=0, vmax=1)
    ax.grid(which="minor", color="w", linestyle='-', linewidth=1)

    # ax.tick_params(which="minor", bottom=False, left=False)
    # ax.set_title("Harvest of local farmers (in tons/year)")
    plt.colorbar(im, extend='max' , ax=ax)
    fig.tight_layout()
    plt.show()
    #plt.close()


vegetables = ["cucumber", "tomato", "lettuce", "asparagus",
              "potato", "wheat", "barley"]
farmers = [1, 2, 3, 4, 5, 6, 7]

harvest = np.array([[0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0],
                    [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0],
                    [1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0],
                    [0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0],
                    [0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0],
                    [1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1],
                    [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3]])

create_graph(harvest, vegetables, farmers)

resulting plot

The above code creates a colormap that goes smoothly from grey at 0 to blue at 1. If you want special colors for values 0 and 1, and a more standard colormap for the values inbetween, you could work with an 'over', an 'under' and a 'bad' color. ('bad' is meant for values that are infinite or not-a-number.) You could make a copy of the 'harvest' matrix and change the high values to 'np.nan' to have them colored special. Unfortunately there isn't an easy way to show the bad color in the colorbar, but the 'under' and the 'over' color can be shown via extend='both' and made rectangular (instead of triangular) with extendrect=True.

from copy import copy

cmap = copy(plt.get_cmap('hot'))
cmap.set_under('grey')
cmap.set_over('steelblue')
cmap.set_bad('green')

im = ax.imshow(np.where(harvest <= 1, harvest, np.nan), cmap=cmap, vmin=0.000001, vmax=0.999999)

plt.colorbar(im, extend='both', extendrect=True, ticks=np.arange(0, 1.01, .1), ax=ax)

3 special colors

Upvotes: 4

Jiadong
Jiadong

Reputation: 2082

I think plt.pcolor is an overkill. All you have to do is to customize a colormap, and apply to imshow. Here is my solution based on your code.

import numpy as np
from datetime import datetime as dt
from datetime import timedelta
import matplotlib
import matplotlib.pyplot as plt
from calendar import monthrange
from matplotlib.colors import ListedColormap, to_rgba

def create_graph(all_rows, task_names, farmers):
    harvest = np.array(all_rows)

    # you have to customize the colormap
    rgba = plt.cm.viridis(range(100))
    ind = int(1/harvest.max()*100)
    rgba[0] = to_rgba('gray')
    rgba[ind+1:] = to_rgba('green')
    cmap1 = ListedColormap(rgba)
    
    fig, ax = plt.subplots()
    ax.imshow(harvest, cmap=cmap1)

    # We want to show all ticks...
    ax.set_xticks(np.arange(len(farmers)))
    ax.set_yticks(np.arange(len(task_names)))
    # ... and label them with the respective list entries
    ax.set_xticklabels(farmers, fontsize=4)
    ax.set_yticklabels(task_names, fontsize=8)
    


    # Rotate the tick labels and set their alignment.
    plt.setp(ax.get_xticklabels(), rotation=0, ha="center", rotation_mode="anchor")
    ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)
    
    #Turn spines off and create white grid.
    for edge, spine in ax.spines.items():
        spine.set_visible(False)


    ax.set_xticks(np.arange(harvest.shape[1]+1)-0.5, minor=True)
    ax.set_yticks(np.arange(harvest.shape[0]+1)-0.5, minor=True)
    ax.grid(which="minor", color="w", linestyle='-', linewidth=1)
    
    # ax.tick_params(which="minor", bottom=False, left=False)
    # ax.set_title("Harvest of local farmers (in tons/year)")
    fig.tight_layout()



vegetables = ["cucumber", "tomato", "lettuce", "asparagus",
                  "potato", "wheat", "barley"]
farmers = [1, 2, 3, 4, 5, 6, 7]
    
harvest = np.array([[0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0],
                    [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0],
                    [1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0],
                    [0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0],
                    [0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0],
                    [1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1],
                    [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3]])
    
create_graph(harvest, vegetables, farmers)

enter image description here

Upvotes: 3

Related Questions