在探索xy图表上有许多点的数据集时,我可以调整alpha和/或标记的大小,以给人一个很好的视觉印象,了解这些点最密集的聚集位置。然而,当我放大或使窗口变大时,需要一个不同的alpha和/或标记大小来给出相同的视觉印象。
当我使窗口变大或放大数据时,如何使alpha值和/或标记大小增加?我在想,如果我把窗口面积翻一番,我可以把标记的大小加倍,或者取α的平方根;相反的是放大。
注意,所有的点都有相同的大小和α。理想的情况下,这个解决方案可以与plot()一起工作,但是如果它只能用分散()来完成的话,这也是有帮助的。
发布于 2018-01-27 03:57:54
您可以通过matplotlib
事件处理实现您想要的结果。您必须分别捕获缩放和调整大小的事件。同时对两者进行解释有点棘手,但也并非不可能。下面是一个有两个子图的例子,左边是直线图,右边是散点图。缩放(因子)和大小的图形(fig_factor)根据缩放因子在图形大小和x-和y-限制重新缩放点。由于定义了两个限制--一个用于x
,另一个用于y
方向,因此我在这里使用了这两个因子各自的最小值。如果您希望使用更大的因素进行扩展,请将两个事件函数中的min
更改为max
。
from matplotlib import pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=1, ncols = 2)
ax1,ax2 = axes
fig_width = fig.get_figwidth()
fig_height = fig.get_figheight()
fig_factor = 1.0
##saving some values
xlim = dict()
ylim = dict()
lines = dict()
line_sizes = dict()
paths = dict()
point_sizes = dict()
## a line plot
x1 = np.linspace(0,np.pi,30)
y1 = np.sin(x1)
lines[ax1] = ax1.plot(x1, y1, 'ro', markersize = 3, alpha = 0.8)
xlim[ax1] = ax1.get_xlim()
ylim[ax1] = ax1.get_ylim()
line_sizes[ax1] = [line.get_markersize() for line in lines[ax1]]
## a scatter plot
x2 = np.random.normal(1,1,30)
y2 = np.random.normal(1,1,30)
paths[ax2] = ax2.scatter(x2,y2, c = 'b', s = 20, alpha = 0.6)
point_sizes[ax2] = paths[ax2].get_sizes()
xlim[ax2] = ax2.get_xlim()
ylim[ax2] = ax2.get_ylim()
def on_resize(event):
global fig_factor
w = fig.get_figwidth()
h = fig.get_figheight()
fig_factor = min(w/fig_width,h/fig_height)
for ax in axes:
lim_change(ax)
def lim_change(ax):
lx = ax.get_xlim()
ly = ax.get_ylim()
factor = min(
(xlim[ax][1]-xlim[ax][0])/(lx[1]-lx[0]),
(ylim[ax][1]-ylim[ax][0])/(ly[1]-ly[0])
)
try:
for line,size in zip(lines[ax],line_sizes[ax]):
line.set_markersize(size*factor*fig_factor)
except KeyError:
pass
try:
paths[ax].set_sizes([s*factor*fig_factor for s in point_sizes[ax]])
except KeyError:
pass
fig.canvas.mpl_connect('resize_event', on_resize)
for ax in axes:
ax.callbacks.connect('xlim_changed', lim_change)
ax.callbacks.connect('ylim_changed', lim_change)
plt.show()
该代码已在Pyton2.7和3.6中使用matplotlib 2.1.1进行了测试。
编辑
在下面的评论和this answer的激励下,我创建了另一个解决方案。这里的主要思想是只使用一种类型的事件,即draw_event
。起初,情节在缩放时没有正确更新。另外,ax.draw_artist()
后面跟着链接答案中的fig.canvas.draw_idle()
并没有真正解决问题(但是,这可能是特定于平台/后端的)。相反,每当缩放更改时,我都添加了对fig.canvas.draw()
的额外调用( if
语句阻止无限循环)。
此外,请避免使用所有全局变量,我将所有内容打包到一个名为MarkerUpdater
的类中。每个Axes
实例都可以单独注册到MarkerUpdater
实例,因此您也可以在一个图中有几个子图,其中一些子图被更新,有些没有更新。我还修正了另一个错误,散点图中的点被错误地缩放了--它们应该是二次的,而不是线性的(see here)。
最后,由于以前的解决方案缺少它,我还添加了标记的alpha
值的更新。这并不像标记大小那样直接,因为alpha
值不能大于1.0。因此,在我的实现中,alpha
值只能从原始值减少。在这里,我实现了这样的方法:当图形大小减小时,alpha
会减少。请注意,如果没有向绘图命令提供alpha
值,则艺术家将None
存储为alpha值。在这种情况下,自动alpha
调优是关闭的。
应该更新哪些内容,其中可以使用Axes
关键字来定义features
--参见下面的if __name__ == '__main__':
,以获得如何使用MarkerUpdater
的示例。
编辑2
正如@ImportanceOfBeingErnest所指出的,在使用TkAgg
后端时,我的答案存在无限递归的问题,而且在缩放(我无法验证,这很可能是依赖于实现)时,该图形显然存在问题。移除fig.canvas.draw()
并在循环中通过Axes
实例添加ax.draw_artist(ax)
,从而解决了这个问题。
编辑3
我更新了代码,以解决在draw_event
上没有正确更新图形的持续问题。这个修正是从这个答案中取下来的,但修改后也适用于几个数字。
在解释如何获得这些因素时,MarkerUpdater
实例包含一个dict
,该dict
存储每个Axes
实例在添加add_ax
时的图形尺寸和轴的限制。例如,在图形调整大小或用户放大数据时触发的draw_event
上,检索图形大小和轴限值的新(当前)值,并计算(并存储)缩放因子,以便缩放和增大图形大小使标记更大。因为x和y维可能以不同的速率变化,所以我使用min
来选择两个计算因子中的一个,并且总是按照这个数字的原始大小进行缩放。
如果希望使用不同的函数缩放alpha,则可以轻松地更改调整alpha值的行。例如,如果你想要幂律而不是线性递减,你可以写path.set_alpha(alpha*facA**n)
,其中n是幂。
from matplotlib import pyplot as plt
import numpy as np
##plt.switch_backend('TkAgg')
class MarkerUpdater:
def __init__(self):
##for storing information about Figures and Axes
self.figs = {}
##for storing timers
self.timer_dict = {}
def add_ax(self, ax, features=[]):
ax_dict = self.figs.setdefault(ax.figure,dict())
ax_dict[ax] = {
'xlim' : ax.get_xlim(),
'ylim' : ax.get_ylim(),
'figw' : ax.figure.get_figwidth(),
'figh' : ax.figure.get_figheight(),
'scale_s' : 1.0,
'scale_a' : 1.0,
'features' : [features] if isinstance(features,str) else features,
}
ax.figure.canvas.mpl_connect('draw_event', self.update_axes)
def update_axes(self, event):
for fig,axes in self.figs.items():
if fig is event.canvas.figure:
for ax, args in axes.items():
##make sure the figure is re-drawn
update = True
fw = fig.get_figwidth()
fh = fig.get_figheight()
fac1 = min(fw/args['figw'], fh/args['figh'])
xl = ax.get_xlim()
yl = ax.get_ylim()
fac2 = min(
abs(args['xlim'][1]-args['xlim'][0])/abs(xl[1]-xl[0]),
abs(args['ylim'][1]-args['ylim'][0])/abs(yl[1]-yl[0])
)
##factor for marker size
facS = (fac1*fac2)/args['scale_s']
##factor for alpha -- limited to values smaller 1.0
facA = min(1.0,fac1*fac2)/args['scale_a']
##updating the artists
if facS != 1.0:
for line in ax.lines:
if 'size' in args['features']:
line.set_markersize(line.get_markersize()*facS)
if 'alpha' in args['features']:
alpha = line.get_alpha()
if alpha is not None:
line.set_alpha(alpha*facA)
for path in ax.collections:
if 'size' in args['features']:
path.set_sizes([s*facS**2 for s in path.get_sizes()])
if 'alpha' in args['features']:
alpha = path.get_alpha()
if alpha is not None:
path.set_alpha(alpha*facA)
args['scale_s'] *= facS
args['scale_a'] *= facA
self._redraw_later(fig)
def _redraw_later(self, fig):
timer = fig.canvas.new_timer(interval=10)
timer.single_shot = True
timer.add_callback(lambda : fig.canvas.draw_idle())
timer.start()
##stopping previous timer
if fig in self.timer_dict:
self.timer_dict[fig].stop()
##storing a reference to prevent garbage collection
self.timer_dict[fig] = timer
if __name__ == '__main__':
my_updater = MarkerUpdater()
##setting up the figure
fig, axes = plt.subplots(nrows = 2, ncols =2)#, figsize=(1,1))
ax1,ax2,ax3,ax4 = axes.flatten()
## a line plot
x1 = np.linspace(0,np.pi,30)
y1 = np.sin(x1)
ax1.plot(x1, y1, 'ro', markersize = 10, alpha = 0.8)
ax3.plot(x1, y1, 'ro', markersize = 10, alpha = 1)
## a scatter plot
x2 = np.random.normal(1,1,30)
y2 = np.random.normal(1,1,30)
ax2.scatter(x2,y2, c = 'b', s = 100, alpha = 0.6)
## scatter and line plot
ax4.scatter(x2,y2, c = 'b', s = 100, alpha = 0.6)
ax4.plot([0,0.5,1],[0,0.5,1],'ro', markersize = 10) ##note: no alpha value!
##setting up the updater
my_updater.add_ax(ax1, ['size']) ##line plot, only marker size
my_updater.add_ax(ax2, ['size']) ##scatter plot, only marker size
my_updater.add_ax(ax3, ['alpha']) ##line plot, only alpha
my_updater.add_ax(ax4, ['size', 'alpha']) ##scatter plot, marker size and alpha
plt.show()
https://stackoverflow.com/questions/48474699
复制