使用 Python 创建填充气泡图

PythonPythonBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在本实验中,我们将学习如何使用 Python 中的 Matplotlib 创建一个填充气泡图。填充气泡图是一种以不同大小的气泡显示数据的图表类型,气泡的大小代表数据的量级。此图表对于显示标量数据很有用,其中数据是与某个项目相关联的单个数值。

虚拟机使用提示

虚拟机启动完成后,点击左上角切换到“笔记本”标签,以访问 Jupyter Notebook 进行练习。

有时,你可能需要等待几秒钟让 Jupyter Notebook 完成加载。由于 Jupyter Notebook 的限制,操作验证无法自动化。

如果你在学习过程中遇到问题,随时向 Labby 提问。课程结束后提供反馈,我们将立即为你解决问题。

导入所需库

要创建填充气泡图,我们需要导入 matplotlib.pyplotnumpy 库。numpy 库用于对数组执行数学运算,这对于计算气泡大小很有用。

import matplotlib.pyplot as plt
import numpy as np

定义数据

我们将在这个示例中使用的数据是不同桌面浏览器的市场份额。我们会将数据定义为一个字典,其中包含每个气泡的浏览器名称、市场份额和颜色。

browser_market_share = {
    'browsers': ['firefox', 'chrome','safari', 'edge', 'ie', 'opera'],
   'market_share': [8.61, 69.55, 8.36, 4.12, 2.76, 2.43],
    'color': ['#5A69AF', '#579E65', '#F9C784', '#FC944A', '#F24C00', '#00B825']
}

定义气泡图类

BubbleChart 类用于创建填充气泡图。该类接受一个气泡面积数组和一个气泡间距值。__init__ 方法设置气泡的初始位置,并计算最大步长距离,即每个气泡在一次迭代中可以移动的距离。

class BubbleChart:
    def __init__(self, area, bubble_spacing=0):
        """
        为气泡收缩进行设置。

        参数
        ----------
        area : 类似数组
            气泡的面积。
        bubble_spacing : 浮点数,默认值:0
            收缩后气泡之间的最小间距。

        注意事项
        -----
        如果“area”是排序的,结果可能看起来很奇怪。
        """
        area = np.asarray(area)
        r = np.sqrt(area / np.pi)

        self.bubble_spacing = bubble_spacing
        self.bubbles = np.ones((len(area), 4))
        self.bubbles[:, 2] = r
        self.bubbles[:, 3] = area
        self.maxstep = 2 * self.bubbles[:, 2].max() + self.bubble_spacing
        self.step_dist = self.maxstep / 2

        ## 计算气泡的初始网格布局
        length = np.ceil(np.sqrt(len(self.bubbles)))
        grid = np.arange(length) * self.maxstep
        gx, gy = np.meshgrid(grid, grid)
        self.bubbles[:, 0] = gx.flatten()[:len(self.bubbles)]
        self.bubbles[:, 1] = gy.flatten()[:len(self.bubbles)]

        self.com = self.center_of_mass()

定义气泡移动方法

BubbleChart 类还包含将气泡移向质心以及检查与其他气泡碰撞的方法。center_of_mass 方法计算所有气泡的质心,center_distance 方法计算一个气泡与质心之间的距离。outline_distance 方法计算一个气泡的轮廓与其他气泡轮廓之间的距离,check_collisions 方法检查新的气泡位置是否与其他气泡发生碰撞。

    def center_of_mass(self):
        return np.average(
            self.bubbles[:, :2], axis=0, weights=self.bubbles[:, 3]
        )

    def center_distance(self, bubble, bubbles):
        return np.hypot(bubble[0] - bubbles[:, 0],
                        bubble[1] - bubbles[:, 1])

    def outline_distance(self, bubble, bubbles):
        center_distance = self.center_distance(bubble, bubbles)
        return center_distance - bubble[2] - \
            bubbles[:, 2] - self.bubble_spacing

    def check_collisions(self, bubble, bubbles):
        distance = self.outline_distance(bubble, bubbles)
        return len(distance[distance < 0])

定义气泡碰撞方法

BubbleChart 类还包含一个用于检查气泡碰撞并移动碰撞气泡周围其他气泡的方法。collides_with 方法计算新气泡位置与其他气泡位置之间的距离。如果距离小于零,则表示发生了碰撞,该方法返回碰撞气泡的索引。collapse 方法将气泡移向质心并在碰撞气泡周围移动,plot 方法在图表上绘制气泡。

    def collides_with(self, bubble, bubbles):
        distance = self.outline_distance(bubble, bubbles)
        idx_min = np.argmin(distance)
        return idx_min if type(idx_min) == np.ndarray else [idx_min]

    def collapse(self, n_iterations=50):
        """
        将气泡移向质心。

        参数
        ----------
        n_iterations : 整数,默认值:50
            要执行的移动次数。
        """
        for _i in range(n_iterations):
            moves = 0
            for i in range(len(self.bubbles)):
                rest_bub = np.delete(self.bubbles, i, 0)
                ## 尝试直接移向质心
                ## 从气泡到质心的方向向量
                dir_vec = self.com - self.bubbles[i, :2]

                ## 缩短方向向量使其长度为 1
                dir_vec = dir_vec / np.sqrt(dir_vec.dot(dir_vec))

                ## 计算新的气泡位置
                new_point = self.bubbles[i, :2] + dir_vec * self.step_dist
                new_bubble = np.append(new_point, self.bubbles[i, 2:4])

                ## 检查新气泡是否与其他气泡碰撞
                if not self.check_collisions(new_bubble, rest_bub):
                    self.bubbles[i, :] = new_bubble
                    self.com = self.center_of_mass()
                    moves += 1
                else:
                    ## 尝试在与你碰撞的气泡周围移动
                    ## 找到碰撞气泡
                    for colliding in self.collides_with(new_bubble, rest_bub):
                        ## 计算方向向量
                        dir_vec = rest_bub[colliding, :2] - self.bubbles[i, :2]
                        dir_vec = dir_vec / np.sqrt(dir_vec.dot(dir_vec))
                        ## 计算正交向量
                        orth = np.array([dir_vec[1], -dir_vec[0]])
                        ## 测试向哪个方向移动
                        new_point1 = (self.bubbles[i, :2] + orth *
                                      self.step_dist)
                        new_point2 = (self.bubbles[i, :2] - orth *
                                      self.step_dist)
                        dist1 = self.center_distance(
                            self.com, np.array([new_point1]))
                        dist2 = self.center_distance(
                            self.com, np.array([new_point2]))
                        new_point = new_point1 if dist1 < dist2 else new_point2
                        new_bubble = np.append(new_point, self.bubbles[i, 2:4])
                        if not self.check_collisions(new_bubble, rest_bub):
                            self.bubbles[i, :] = new_bubble
                            self.com = self.center_of_mass()

            if moves / len(self.bubbles) < 0.1:
                self.step_dist = self.step_dist / 2

    def plot(self, ax, labels, colors):
        """
        绘制气泡图。

        参数
        ----------
        ax : matplotlib.axes.Axes
        labels : 列表
            气泡的标签。
        colors : 列表
            气泡的颜色。
        """
        for i in range(len(self.bubbles)):
            circ = plt.Circle(
                self.bubbles[i, :2], self.bubbles[i, 2], color=colors[i])
            ax.add_patch(circ)
            ax.text(*self.bubbles[i, :2], labels[i],
                    horizontalalignment='center', verticalalignment='center')

创建气泡图对象并绘制图表

要创建填充气泡图,我们需要创建一个 BubbleChart 对象,并调用 collapse 方法将气泡移向质心。然后,我们可以创建一个 matplotlib 图形并向其中添加一个坐标轴对象。最后,我们调用 plot 方法在图表上绘制气泡。

bubble_chart = BubbleChart(area=browser_market_share['market_share'],
                           bubble_spacing=0.1)

bubble_chart.collapse()

fig, ax = plt.subplots(subplot_kw=dict(aspect="equal"))
bubble_chart.plot(
    ax, browser_market_share['browsers'], browser_market_share['color'])
ax.axis("off")
ax.relim()
ax.autoscale_view()
ax.set_title('Browser market share')

plt.show()

总结

在本实验中,我们学习了如何使用 Python 中的 Matplotlib 创建填充气泡图。我们将数据定义为一个字典,其中包含每个气泡的浏览器名称、市场份额和颜色。然后,我们创建了一个 BubbleChart 类来设置气泡的初始位置、计算最大步长距离、将气泡移向质心以及检查与其他气泡的碰撞。最后,我们使用 matplotlib 绘制了图表。