在 Matplotlib 中制作非重叠气泡图(圆形包装) [英] Making a non-overlapping bubble chart in Matplotlib (circle packing)

查看:77
本文介绍了在 Matplotlib 中制作非重叠气泡图(圆形包装)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试在 Matplotlib 中制作气泡图,其中气泡不重叠,因此在图表中填充圆圈/气泡,大致类似于

然后,您让圆圈随机行走,并在每一步中检查一堆冰柱的势能"是否变小以及所获得的位置是否有效(即没有重叠).

if (e 

作为势",我们可以简单地使用方形径向函数.

  self.p = lambda x,y:np.sum((x ** 2 + y ** 2)** 2)

代码:

将 numpy 导入为 np导入matplotlib.pyplot作为plt# 创建 10 个不同半径的圆r = np.random.randint(5,15, size=10)C类():def __init __(self,r):self.N = len(r)self.x = np.ones((self.N,3))self.x[:,2] = rmaxstep = 2*self.x[:,2].max()长度 = np.ceil(np.sqrt(self.N))网格 = np.arange(0,length*maxstep,maxstep)gx,gy = np.meshgrid(grid,grid)self.x[:,0] = gx.flatten()[:self.N]self.x [:,1] = gy.flatten()[:self.N]self.x[:,:2] = self.x[:,:2] - np.mean(self.x[:,:2], axis=0)self.step = self.x[:,2].min()self.p = lambda x,y:np.sum((x ** 2 + y ** 2)** 2)self.E = self.energy()self.iter = 1.定义最小化(自我):而 self.iter <1000*self.N:对于我在范围内(self.N):rand = np.random.randn(2)*self.step/self.iterself.x [i,:2] + = rande = self.energy()if (e < self.E and self.isvalid(i)):self.E = eself.iter = 1.别的:self.x[i,:2] -= randself.iter + = 1.def energy(self):返回 self.p(self.x[:,0], self.x[:,1])def距离(self,x1,x2):返回np.sqrt((x1 [0] -x2 [0])** 2+(x1 [1] -x2 [1])** 2)-x1 [2] -x2 [2]def isvalid(self, i):对于范围内的 j(self.N):如果我!= j:如果 self.distance(self.x[i,:], self.x[j,:]) <0:返回错误返回Truedef图(self,ax):对于我在范围内(self.N):circ = plt.Circle(self.x[i,:2],self.x[i,2])ax.add_patch(circ)c = C(r)图, ax = plt.subplots(subplot_kw=dict(aspect="equal"))ax.axis("off")c.minimize()c.plot(ax)ax.relim()ax.autoscale_view()plt.show()

由于随机游走的性质,找到解决方案需要一点时间(在这种情况下约 10 秒);您当然可以使用参数(主要是步骤数1000*self.N,直到解决方案得到解决),看看什么适合您的需求.

I am currently trying to make a bubble chart in Matplotlib where the bubbles do not overlap, so packing the circles/bubbles in the chart, approximately like this.

What I thought might work:

  • Plot the first data-point using x = 1, y = 1
  • Randomly plotting the other data-points by calculating the radius of the bubble given the scalar value as to avoid overlapping

But I have not been able to really implement it and could not find anything on this.

解决方案

The following would be a brute-force approach.
You can first place all circles on a grid, with a gridspacing as large as twice the maximum radius of any of the circles.

Then you let the circles do a random walk and check in each step if the "potential energy" of the bunch of cicles has become smaller and if the positions obtained are valid (i.e. no overlaps).

if (e < self.E and self.isvalid(i)):

As a "potential" we can simply use a square radial function.

self.p = lambda x,y: np.sum((x**2+y**2)**2)

The code:

import numpy as np
import matplotlib.pyplot as plt

# create 10 circles with different radii
r = np.random.randint(5,15, size=10)

class C():
    def __init__(self,r):
        self.N = len(r)
        self.x = np.ones((self.N,3))
        self.x[:,2] = r
        maxstep = 2*self.x[:,2].max()
        length = np.ceil(np.sqrt(self.N))
        grid = np.arange(0,length*maxstep,maxstep)
        gx,gy = np.meshgrid(grid,grid)
        self.x[:,0] = gx.flatten()[:self.N]
        self.x[:,1] = gy.flatten()[:self.N]
        self.x[:,:2] = self.x[:,:2] - np.mean(self.x[:,:2], axis=0)

        self.step = self.x[:,2].min()
        self.p = lambda x,y: np.sum((x**2+y**2)**2)
        self.E = self.energy()
        self.iter = 1.

    def minimize(self):
        while self.iter < 1000*self.N:
            for i in range(self.N):
                rand = np.random.randn(2)*self.step/self.iter
                self.x[i,:2] += rand
                e = self.energy()
                if (e < self.E and self.isvalid(i)):
                    self.E = e
                    self.iter = 1.
                else:
                    self.x[i,:2] -= rand
                    self.iter += 1.

    def energy(self):
        return self.p(self.x[:,0], self.x[:,1])

    def distance(self,x1,x2):
        return np.sqrt((x1[0]-x2[0])**2+(x1[1]-x2[1])**2)-x1[2]-x2[2]

    def isvalid(self, i):
        for j in range(self.N):
            if i!=j: 
                if self.distance(self.x[i,:], self.x[j,:]) < 0:
                    return False
        return True

    def plot(self, ax):
        for i in range(self.N):
            circ = plt.Circle(self.x[i,:2],self.x[i,2] )
            ax.add_patch(circ)

c = C(r)

fig, ax = plt.subplots(subplot_kw=dict(aspect="equal"))
ax.axis("off")

c.minimize()

c.plot(ax)
ax.relim()
ax.autoscale_view()
plt.show()

Because of the random walk nature of this, finding the solution will take a little time (~10 seconds in this case); you may of course play with the parameters (mainly the number of steps 1000*self.N until a solution is fixed) and see what suits your needs.

这篇关于在 Matplotlib 中制作非重叠气泡图(圆形包装)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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