标记大小/alpha缩放与绘图/散点图的窗口大小/缩放 [英] Marker size/alpha scaling with window size/zoom in plot/scatter

查看:94
本文介绍了标记大小/alpha缩放与绘图/散点图的窗口大小/缩放的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 xy 图表上探索具有多个点的数据集时,我可以调整 alpha 和/或标记大小,以便快速直观地了解这些点的聚集最密集的位置.但是,当我放大或使窗口变大时,需要不同的 alpha 和/或标记大小才能提供相同的视觉印象.

When exploring data sets with many points on an xy chart, I can adjust the alpha and/or marker size to give a good quick visual impression of where the points are most densely clustered. However when I zoom in or make the window bigger, the a different alpha and/or marker size is needed to give the same visual impression.

当我增大窗口或放大数据时,如何增加alpha值和/或标记大小?我在想,如果我将窗口面积加倍,我可以将标记大小加倍,和/或取 alpha 的平方根;放大则相反.

How can I have the alpha value and/or the marker size increase when I make the window bigger or zoom in on the data? I am thinking that if I double the window area I could double the marker size, and/or take the square root of the alpha; and the opposite for zooming.

请注意,所有点的大小和alpha均相同.理想情况下,该解决方案适用于 plot(),但如果它只能使用 scatter() 完成,那也会有帮助.

Note that all points have the same size and alpha. Ideally the solution would work with plot(), but if it can only be done with scatter() that would be helpful also.

推荐答案

您可以使用 matplotlib 事件处理实现您想要的.您必须分别捕获缩放和调整大小事件.同时考虑两者有点棘手,但并非不可能.下面是一个包含两个子图的示例,左侧是线图,右侧是散点图.图形的缩放(因数)和大小调整(fig_factor)都根据图形大小以及x和y极限中的比例因子来重新缩放点.由于定义了两个限制,一个限制为 x ,一个限制为 y 方向,因此我在此使用了两个因素的最小值.如果您希望使用更大的因子进行缩放,请将两个事件函数中的 min 更改为 max.

You can achieve what you want with matplotlib event handling. You have to catch zoom and resize events separately. It's a bit tricky to account for both at the same time, but not impossible. Below is an example with two subplots, a line plot on the left and a scatter plot on the right. Both zooming (factor) and resizing of the figure (fig_factor) re-scale the points according to the scaling factors in figure size and x- and y- limits. As there are two limits defined -- one for the x and one for the y direction, I used here the respective minima for the two factors. If you'd rather want to scale with the larger factors, change the min to max in both event functions.

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()

该代码已在Pyton 2.7和3.6中与matplotlib 2.1.1进行了测试.

The code has been tested in Pyton 2.7 and 3.6 with matplotlib 2.1.1.

编辑

受以下评论和此答案的激励,我创建了另一个解决方案.这里的主要思想是仅使用一种类型的事件,即 draw_event .起初,这些图在缩放时没有正确更新.另外 ax.draw_artist() 后跟 fig.canvas.draw_idle() 就像在链接的答案中一样并没有真正解决问题(但是,这可能是平台/后端具体的).相反,我在缩放发生变化时添加了对 fig.canvas.draw() 的额外调用(if 语句可防止无限循环).

Motivated by the comments below and this answer, I created another solution. The main idea here is to only use one type of event, namely draw_event. At first the plots did not update correctly upon zooming. Also ax.draw_artist() followed by a fig.canvas.draw_idle() like in the linked answer did not really solve the problem (however, this might be platform/backend specific). Instead I added an extra call to fig.canvas.draw() whenever the scaling changes (the if statement prevents infinite loops).

此外,请避免使用所有全局变量,我将所有内容都封装到一个名为 MarkerUpdater 的类中.每个 Axes 实例都可以单独注册到 MarkerUpdater 实例,因此您也可以在一个图中有多个子图,其中一些已更新,有些未更新.我还修复了另一个错误,其中散点图中的点缩放错误 - 它们应该按二次而不是线性缩放(参见此处).

In addition, do avoid all the global variables, I wrapped everything into a class called MarkerUpdater. Each Axes instance can be registered separately to the MarkerUpdater instance, so you could also have several subplots in one figure, of which some are updated and some not. I also fixed another bug, where the points in the scatter plot scaled wrongly -- they should scale quadratic, not linear (see here).

最后,由于之前的解决方案中缺少它,我还为标记的 alpha 值添加了更新.这不如标记大小那么直接,因为 alpha 值不能大于1.0.因此,在我的实现中,alpha 值只能从原始值减少.在这里,我实现了它,使得当图形尺寸减小时, alpha 减小.请注意,如果没有为绘图命令提供 alpha 值,则艺术家将 None 存储为 alpha 值.在这种情况下,自动 alpha 调整关闭.

Finally, as it was missing from the previous solution, I also added updating for the alpha value of the markers. This is not quite as straight forward as the marker size, because alpha values must not be larger than 1.0. For this reason, in my implementation the alpha value can only be decreased from the original value. Here I implemented it such that the alpha decreases when the figure size is decreased. Note that if no alpha value is provided to the plot command, the artist stores None as alpha value. In this case the automatic alpha tuning is off.

应该更新哪些Axes 可以用features 关键字定义——见下文if __name__ == '__main__':例如如何使用 MarkerUpdater.

What should be updated in which Axes can be defined with the features keyword -- see below if __name__ == '__main__': for an example how to use MarkerUpdater.

编辑 2

正如@ImportanceOfBeingErnest所指出的那样,在使用 TkAgg 后端时,我的答案存在无限递归的问题,而且显然在缩放时图形无法正确刷新(我无法这样做)验证,因此很可能取决于实现).删除 fig.canvas.draw() 并在 Axes 实例的循环中添加 ax.draw_artist(ax) 来解决此问题.

As pointed out by @ImportanceOfBeingErnest, there was a problem with infinite recursion with my answer when using the TkAgg backend, and apparently problems with the figure not refreshing properly upon zooming (which I couldn't verify, so probably that was implementation dependent). Removing the fig.canvas.draw() and adding ax.draw_artist(ax) within the loop over the Axes instances instead fixed this issue.

编辑3

我更新了代码以解决一个持续存在的问题,即在 draw_event 上图形不能正确更新.修复是从这个答案中获取的,但经过修改后也适用于几个数字.

I updated the code to fix an ongoing issue where figure is not updated properly upon a draw_event. The fix was taken from this answer, but modified to also work for several figures.

就如何获得因子的解释而言,MarkerUpdater 实例包含一个 dict,它为每个 Axes 实例存储数字 add_ax 添加时的尺寸和轴的限制.在 draw_event 时,例如在调整图形大小或用户放大数据时触发,将检索图形大小和轴限制的新(当前)值并计算缩放因子(并存储),以便放大并增加图形尺寸会使标记更大.因为 x 和 y 维度可能以不同的速率变化,所以我使用 min 选择两个计算因子之一,并始终根据图形的原始大小进行缩放.

In terms of an explanation of how the factors are obtained, the MarkerUpdater instance contains a dict that stores for each Axes instance the figure dimensions and the limits of the axes at the time it is added with add_ax. Upon a draw_event, which is for instance triggered when the figure is resized or the user zooms in on the data, the new (current) values for figure size and axes limits are retrieved and a scaling factor is calculated (and stored) such that zooming in and increasing the figure size makes the markers bigger. Because x- and y-dimensions may change at different rates, I use min to pick one of the two calculated factors and always scale against the original size of the figure.

如果要使用其他功能缩放Alpha,则可以轻松更改用于调整Alpha值的线.例如,如果您想要幂律而不是线性递减,您可以编写 path.set_alpha(alpha*facA**n),其中 n 是幂.

If you want your alpha to scale with a different function, you can easily change the lines that adjust the alpha value. For instance, if you want a power law instead of a linear decrease, you can write path.set_alpha(alpha*facA**n), where n is the power.

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()

这篇关于标记大小/alpha缩放与绘图/散点图的窗口大小/缩放的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆