Han Zhengzu
Han Zhengzu

Reputation: 3852

Changing line style proportional spacing using matplotlib

With uneven distributed data, the color are too similar for almost all points. For example. the figure below represent too much shallow color corresponding to the value < 12000.

enter image description here

So, I thought about using Pysal to break the data range into several class and allot the colormap evenly to these classes.

Here is my attempt

My cursory work

from pysal.esda.mapclassify import Natural_Breaks as nb
import pysal.esda.mapclassify as mpc
from matplotlib.colors import Normalize

my_bins = [ 20,200,600,1600, 4000, 36000]    
breaks =mpc.User_Defined(value,my_bins)     

bin_labels = ["%0.0f" % b for b in breaks.bins]
bins = pd.DataFrame({'Class': breaks.yb},)  

Then, each point has a class based on its value's data range.

fig = plt.figure()
ax  =plt.subplot()
cmap = plt.get_cmap('coolwarm')
norm1 = Normalize(vmin=bins['Class'].min(), vmax=bins['Class'].max())
em_plot = plt.scatter(px[:],py[:], linewidth='0',c =   
np.array(bins['Class']),s =(value**0.4)*10, cmap = cmap,\
                  norm = norm1,alpha = 0.7)
cbaxes = fig.add_axes([0.6625, 0.175, 0.175, 0.04]) 
cbar = plt.colorbar(em_plot,cax=cbaxes,orientation='horizontal')
value_list = np.array([1,3,5,]) ### THE CLASS
value_label = ['200', '1600','36000'] ### THE VALUE CORRESPONDING TO CLASS
cbar.set_ticks(value_list)
cbar.set_ticklabels(value_label)
cbar.ax.tick_params(labelsize=8) 

enter image description here

My target

Instead of the bar style color-legend, I want to label the scatter point with discrete circles: Each circle represent the size and color for specific value on main figure.

An illustration which I found on the Internet shown here:

enter image description here

As the figure above shown, the percentage legend with different color would be my ideal target!

Any advice would be appreciate!

Update

Thanks for the answer. I have tried your method. But it seems that not fully fit my target.

Here is my figure which take the answer as reference.

enter image description here

From the legend and colorbar, we could found that the color are not the same due to the different mapping strategy(colorbar -> classify result; legend -> REAL VALUE)

Then, I do some adjustment like this:

cmap = plt.get_cmap('coolwarm')
colors=cmap(np.arange(6)/6.0)

ls = [Line2D(range(1), range(1), linewidth=0,  
      color=colors[np.where(np.array(my_bins) == v)[0]], \      
      marker='o', ms=(v**0.2)*2) for v in my_bins]

plt.legend(ls,my_bins)  

But it went wrong:

ValueError: to_rgba: Invalid rgba arg "[[ 0.2298057 0.29871797 0.75368315 1. ]]" length of rgba sequence should be either 3 or 4

Update 2

Thanks for Davis's answer, I want to generate the index of the element of my_bins. So, I tried to get the indice by using np.where(my_bins == element).
But it works failed. Then, I tried to do it using:

 ..., color = colors[i], ms=(v**0.2)*2) for i,v in enumerate(my_bins)

It works!

Upvotes: 2

Views: 218

Answers (1)

Diziet Asahi
Diziet Asahi

Reputation: 40747

I think the "easiest" would be to create a proxy artist for your legend.

The following works, but it needs to be adjusted to your needs, I'm not sure the normalization of the colormap and the size of the points is quite right.

from matplotlib.lines import Line2D
cmap = plt.get_cmap('coolwarm')
my_bins = [20,200,600,1600, 4000, 36000]
ls = [Line2D(range(1), range(1), linewidth=0, color=cmap(v), marker='o', ms=(v**0.2)) for v in my_bins]
plt.plot()
plt.legend(ls,my_bins)

enter image description here

UPDATE regarding your updated problem: your issue is with color=colors[np.where(np.array(my_bins) == v)[0]]. Truthfully, I have not idea what you are trying to do here. You should pass a (6, 4) array, but instead you passed a (1, 4) array.

If I understand your code, you should maybe try this:

cmap = plt.get_cmap('coolwarm')
colors=cmap(np.arange(6)/6.0)

ls = [Line2D(range(1), range(1), linewidth=0,  
      color=colors[i], \      
      marker='o', ms=(v**0.2)*2) for i,v in enumerate(my_bins)]

plt.legend(ls,my_bins) 

Upvotes: 1

Related Questions