slothfulwave612
slothfulwave612

Reputation: 1409

Matplotlib: Add border around group of bins with most frequent values in hexbin plot

I am making a hexbin plot with the following Python script:

pitch = Pitch(
    line_color="#747474", pitch_color="#222222", orientation="vertical", half=True, plot_arrow=False
)

fig, ax = pitch.create_pitch()

## color-map
cmap = [
    "#222222", "#3A2527", "#52282B", "#6A2B30", 
    "#762C32", "#822D34", "#8E2F37", "#9A3039", 
    "#B2323D", "#BE3440", "#CA3542", "#E13746"
]
cmap = colors.ListedColormap(cmap)

hexbin = ax.hexbin(
    68 - shots_data['Y'], shots_data['X'], zorder=3, cmap=cmap,
    extent=(0, 68, 52, 104), gridsize=22, bins=13, ec="#222222", lw=3
)

The above code produces the following output:

enter image description here

Now I want to add borders around the hexagons with the most frequent values, which will look something like this. Note that in the below image the white borders are hand drawn to show how the result will look like. I don't know how to do this. What should I add in the code to produce such result. enter image description here

Edit:

I am getting some results but they are not perfect, here is the updated script:

## Pitch obejct
pitch = Pitch(
    line_color="#747474", pitch_color="#222222", orientation="vertical", half=True, plot_arrow=False
)

## create-pitch
fig, ax = pitch.create_pitch()

## colormap
cmap = [
    "#3A2527", "#52282B", "#6A2B30", "#822D34",
    "#822D34","#882E36", "#8E2F37", "#9A3039", "#B2323D", "#E13746"
]
cmap = colors.ListedColormap(cmap)

## extent
extent = (
    shots_data['Y'].min(), shots_data['Y'].max(),
    shots_data['X'].min(), shots_data['X'].max(),
)

## main hexbin
hexbin = ax.hexbin(
    68 - shots_data['Y'], shots_data['X'], zorder=3, cmap=cmap,
    extent=extent, gridsize=22, ec="#222222", lw=1, bins="log", mincnt=1
)

## hexbin with mincnt=6
cmap = [
     "#822D34", "#882E36", "#8E2F37", "#9A3039", "#B2323D", "#E13746"
]
cmap = colors.ListedColormap(cmap)
ax.hexbin(
    68 - shots_data['Y'], shots_data['X'], zorder=3, cmap=cmap,
    extent=extent, gridsize=22, ec="#bce7ef", lw=1, bins="log", mincnt=6
)


## add rectangle 
rect = plt.Rectangle(
    xy=(-0.1, 104), width=68.1, height=1, zorder=3, fc="#222222"
)
ax.add_patch(rect)

This is producing the following result: enter image description here

Upvotes: 5

Views: 798

Answers (1)

Marc
Marc

Reputation: 734

I worked out two versions for plotting a contour line for the hexagons.

(header)

import numpy as np, matplotlib.pyplot as plt, matplotlib.colors

# color-map
cmap = [ "#222222", "#3A2527", "#52282B", "#6A2B30", "#762C32", "#822D34", 
        "#8E2F37", "#9A3039", "#B2323D", "#BE3440", "#CA3542", "#E13746"]
cmap = matplotlib.colors.ListedColormap(cmap)

#prepare data
np.random.seed(10)
shotsX = np.random.randn(1000)*20+10
shotsY = np.random.randn(1000)*15+50

#original plot
cfg = dict(x=shotsX, y=shotsY, cmap=cmap, gridsize=22, extent=[0,100,0,100])
h   = plt.hexbin( ec="#222222",lw=2,zorder=-3,**cfg)
plt.axis('off');

1) white glow

This approach is similar to your edit. Call plt.hexbin multiple time with different lines styles as well as with the parameter mincnt:

#draw thick white contours + overlay previous style
cfg = {**cfg,'vmin':h.get_clim()[0], 'vmax':h.get_clim()[1]}
plt.hexbin( ec="white"  ,lw=5,zorder=-2,mincnt=10,**cfg)
plt.hexbin( ec="#222222",lw=2,zorder=-1,mincnt=10,**cfg)
plt.xlim(-3,103) #required as second call of plt.hexbin()
plt.ylim(-3,103) #strangely affects the limits ...

enter image description here

Strictly speaking the "glow" adds a highlight to hexagons with many counts.

2) Contours

Drawing only white contour lines on top of the original hexagons is more complicated. You can solve this by

  • finding the vertices (ie center) of the hexagons
  • for each vertex calculate the segment lines
  • extract outer lines
  • draw
def hexLines(a=None,i=None,off=[0,0]):
    '''regular hexagon segment lines as `(xy1,xy2)` in clockwise 
    order with points in line sorted top to bottom
    for irregular hexagon pass both `a` (vertical) and `i` (horizontal)'''
    if a is None: a = 2 / np.sqrt(3) * i;    
    if i is None: i = np.sqrt(3) / 2 * a;     
    h  = a / 2 
    xy = np.array([ [ [ 0, a], [ i, h] ], 
                    [ [ i, h], [ i,-h] ], 
                    [ [ i,-h], [ 0,-a] ], 
                    [ [-i,-h], [ 0,-a] ], #flipped
                    [ [-i, h], [-i,-h] ], #flipped
                    [ [ 0, a], [-i, h] ]  #flipped
                  ])  
    return xy+off;


#get hexagon centers that should be highlighted
verts = h.get_offsets()
cnts  = h.get_array()
highl = verts[cnts > .5*cnts.max()]

#create hexagon lines
a = ((verts[0,1]-verts[1,1])/3).round(6)
i = ((verts[1:,0]-verts[:-1,0])/2).round(6)
i = i[i>0][0]
lines = np.concatenate([hexLines(a,i,off) for off in highl])

#select contour lines and draw
uls,c = np.unique(lines.round(4),axis=0,return_counts=True)
for l in uls[c==1]: plt.plot(*l.transpose(),'w-',lw=2,scalex=False,scaley=False)

enter image description here

Note: Finding matching contour lines depends on the float accuracy np.unique(lines.round(5),...), here a rounded to 4 decimals. Depending on input data this might have to be adjusted.

Upvotes: 2

Related Questions