Anti-Grain Geometry (AGG) & Matplotlib Artists
xxxxxxxxxx
import numpy as np,pandas as pd,pylab as pl
import matplotlib.transforms as mtrans
from matplotlib.colors import LightSource
from matplotlib.artist import Artist
from matplotlib.patheffects import Normal
def smooth(data,sigma=3):
def smooth1d(x,window_len):
s=np.r_[2*x[0]-x[window_len:1:-1],x,2*x[-1]-x[-1:-window_len:-1]]
w=np.hanning(window_len)
y=np.convolve(w/w.sum(),s,mode='same')
return y[window_len-1:-window_len+1]
def smooth2d(X,window_len):
for axis in (0,1):
X=np.apply_along_axis(smooth1d,axis,X,window_len)
return X
window_len=max(int(sigma),3)*2+1
if len(data.shape)==1:
y=smooth1d(data,window_len)
elif len(data.shape)==2:
y=smooth2d(data,window_len)
else:
raise ValueError(f"Data must have 1 or 2 dimensions, "+\
f"but has {len(data.shape)} dimensions instead.")
return y
xxxxxxxxxx
class BaseFilter:
def get_pad(self,dpi):
return 0
def process_image(self,padded_src,dpi):
raise NotImplementedError("Should be overridden by subclasses")
def __call__(self,img,dpi):
pad=self.get_pad(dpi)
padded_img=np.pad(img,[(pad,pad),(pad,pad),(0,0)],"constant")
return self.process_image(padded_img,dpi),-pad,-pad
class OffsetFilter(BaseFilter):
def __init__(self,offsets=(0,0)):
self.offsets=offsets
def get_pad(self,dpi):
return int(max(self.offsets)/72*dpi)
def process_image(self,padded_img,dpi):
ox,oy=self.offsets
a1=np.roll(padded_img,int(ox/72*dpi),axis=1)
a2=np.roll(a1,-int(oy/72*dpi),axis=0)
return a2
class GaussianFilter(BaseFilter):
def __init__(self,sigma,alpha=.5,color=(0,0,0)):
self.sigma=sigma
self.alpha=alpha
self.color=color
def get_pad(self, dpi):
return int(self.sigma*3/72*dpi)
def process_image(self,padded_img,dpi):
img=np.empty_like(padded_img)
img[:,:,:3]=self.color
img[:,:,3] = smooth(padded_img[:,:,3]*self.alpha,self.sigma/72*dpi)
return img
xxxxxxxxxx
class DropShadowFilter(BaseFilter):
def __init__(self,sigma,alpha=.3,color=(0,0,0),offsets=(0,0)):
self.gauss_filter=GaussianFilter(sigma,alpha,color)
self.offset_filter=OffsetFilter(offsets)
def get_pad(self,dpi):
return max(self.gauss_filter.get_pad(dpi),self.offset_filter.get_pad(dpi))
def process_image(self,padded_img,dpi):
t1=self.gauss_filter.process_image(padded_img,dpi)
t2=self.offset_filter.process_image(t1,dpi)
return t2
class LightFilter(BaseFilter):
def __init__(self,sigma,fraction=1):
self.gauss_filter=GaussianFilter(sigma,alpha=1)
self.light_source=LightSource()
self.fraction=fraction
def get_pad(self,dpi):
return self.gauss_filter.get_pad(dpi)
def process_image(self,padded_img,dpi):
t1=self.gauss_filter.process_image(padded_img,dpi)
elevation=t1[:,:,3]
rgb=padded_img[:,:,:3]
alpha=padded_img[:,:,3:]
rgb2=self.light_source.shade_rgb(
rgb,elevation,fraction=self.fraction,blend_mode="overlay")
return np.concatenate([rgb2,alpha],-1)
class GrowFilter(BaseFilter):
def __init__(self,pixels,color=(1,1,1)):
self.pixels=pixels
self.color=color
def __call__(self,img,dpi):
alpha=np.pad(img[...,3],self.pixels,"constant")
alpha2=np.clip(smooth(alpha,self.pixels/72*dpi)*5,0,1)
new_img=np.empty((*alpha2.shape,4))
new_img[:,:,:3]=self.color
new_img[:,:,3]=alpha2
offsetx,offsety=-self.pixels,-self.pixels
return new_img,offsetx,offsety
class FilteredArtistList(Artist):
def __init__(self,artist_list,filter):
super().__init__()
self._artist_list =artist_list
self._filter=filter
def draw(self,renderer):
renderer.start_rasterizing()
renderer.start_filter()
for a in self._artist_list:
a.draw(renderer)
renderer.stop_filter(self._filter)
renderer.stop_rasterizing()
xxxxxxxxxx
def create_data(xmin,xmax,ymin,ymax,delta):
x=np.arange(xmin,xmax,delta)
y=np.arange(ymin,ymax,delta)
X,Y=np.meshgrid(x,y)
Z1=np.exp(-X**2-Y**2)
Z2=np.exp(-(X-1)**2-(Y-1)**2)
return Z1-Z2
def filtered_text(ax,xmin=-2.5,xmax=3,ymin=-2.5,ymax=3,delta=.025):
Z=create_data(xmin=xmin,xmax=xmax,ymin=ymin,ymax=ymax,delta=delta)
ax.imshow(Z,interpolation='bilinear',origin='lower',cmap=pl.cm.bwr,
extent=(xmin,xmax,ymin,ymax),aspect='auto')
levels=np.arange(np.min(Z),np.max(Z),.1)
CS=ax.contour(Z,levels,origin='lower',linewidths=2,
extent=(xmin,xmax,ymin,ymax),cmap=pl.cm.bwr_r)
cl=ax.clabel(CS,levels[1::2],inline=True,fmt='%1.1f',fontsize=12)
for t in cl:
t.set_color("k"); t.set_path_effects([Normal()])
white_glows=FilteredArtistList(cl,GrowFilter(5))
ax.add_artist(white_glows)
white_glows.set_zorder(cl[0].get_zorder()-.1)
def drop_shadow_patches(
ax,group1,group2,colors=['#3636ff','#ff3636'],edge_color='whitesmoke'):
n=len(group1)
width=.35
rects1=ax.bar(
np.arange(n),group1,width,color=colors[0],ec=edge_color,lw=2)
rects2=ax.bar(
np.arange(n)+width+.1,group2,width,color=colors[1],ec=edge_color,lw=2)
drop=DropShadowFilter(9,offsets=(1,1))
shadow=FilteredArtistList(rects1+rects2,drop)
ax.add_artist(shadow)
shadow.set_zorder(rects1[0].get_zorder()-.1)
ax.set_ylim(0,1.2*max(group1+group2))
ax.xaxis.set_visible(False)
fix,axs=pl.subplots(1,2,figsize=(6.7,4))
drop_shadow_patches(axs[0],[.1,.35,.17,.28,.53],[.15,.42,.34,.41,.25])
filtered_text(axs[1]); pl.show()