Let the data move! Use Python to create animated visualizations, so that data is no longer boring!

Usually, most of the charts made by everyone are static, and sometimes they are not attractive enough.

Today, Xiao F will introduce to you how to draw dynamic charts with Python.

It mainly uses Matplotlib+imageio, of which Matplotlib has an Animation class that can generate animated GIFs, but the learning cost is high, and it is still difficult to use.

Here I will first create a picture of a static chart, and then use Imageio to create a GIF (dynamic chart).

A total of three types of dynamic charts are introduced to you: line chart, bar chart, and scatter chart.

01 Line Chart

Let's draw a simple line chart first.

import os
import numpy as np
import matplotlib.pyplot as plt
import imageio

# 生成40个取值在30-40的数
y = np.random.randint(30, 40, size=(40))
# 绘制折线
plt.plot(y)
# 设置y轴最小值和最大值
plt.ylim(20, 50)

# 显示
plt.show()

Using Numpy to create a list of random integers in the range 30 to 40, the result is as follows.

The following will slice the list of integers to generate a graph of the different stages.

# 第一张图
plt.plot(y[:-3])
plt.ylim(20, 50)
plt.savefig('1.png')
plt.show()

# 第二张图
plt.plot(y[:-2])
plt.ylim(20, 50)
plt.savefig('2.png')
plt.show()

# 第三张图
plt.plot(y[:-1])
plt.ylim(20, 50)
plt.savefig('3.png')
plt.show()

# 第四张图
plt.plot(y)
plt.ylim(20, 50)
plt.savefig('4.png')
plt.show()

Get four line charts with x-axis as 0:36, 0:37, 0:38, 0:39.

With these four images, we can use Imageio to generate GIFs.

# 生成Gif
with imageio.get_writer('mygif.gif', mode='I') as writer:
    for filename in ['1.png', '2.png', '3.png', '4.png']:
        image = imageio.imread(filename)
        writer.append_data(image)

The animation is here.

A moving line chart is created, but it does not start when the x-axis coordinate is 0.

filenames = []
num = 0
for i in y:
    num += 1
    # 绘制40张折线图
    plt.plot(y[:num])
    plt.ylim(20, 50)

    # 保存图片文件
    filename = f'{num}.png'
    filenames.append(filename)
    plt.savefig(filename)
    plt.close()

# 生成gif
with imageio.get_writer('mygif.gif', mode='I') as writer:
    for filename in filenames:
        image = imageio.imread(filename)
        writer.append_data(image)

# 删除40张折线图
for filename in set(filenames):
    os.remove(filename)

Draw 40 line charts, save the pictures, and generate GIFs.

You can see that the x-coordinate of the line chart goes from 0 to 40.

02 Bar Chart

The line chart above only needs one y value at a time, while the bar chart needs all the y values ​​so that all the bars can move at the same time.

Create fixed values ​​for the X-axis, lists for the Y-axis, and use Matplotlib's bar chart functions.

x = [1, 2, 3, 4, 5]
coordinates_lists = [[0, 0, 0, 0, 0],
                     [10, 30, 60, 30, 10],
                     [70, 40, 20, 40, 70],
                     [10, 20, 30, 40, 50],
                     [50, 40, 30, 20, 10],
                     [75, 0, 75, 0, 75],
                     [0, 0, 0, 0, 0]]
filenames = []
for index, y in enumerate(coordinates_lists):
    # 条形图
    plt.bar(x, y)
    plt.ylim(0, 80)

    # 保存图片文件
    filename = f'{index}.png'
    filenames.append(filename)

    # 重复最后一张图形15帧(数值都为0),15张图片
    if (index == len(coordinates_lists) - 1):
        for i in range(15):
            filenames.append(filename)

    # 保存
    plt.savefig(filename)
    plt.close()

# 生成gif
with imageio.get_writer('mygif.gif', mode='I') as writer:
    for filename in filenames:
        image = imageio.imread(filename)
        writer.append_data(image)

# 删除20张柱状图
for filename in set(filenames):
    os.remove(filename)

There are 5 bar graph pictures with numerical values, and 2+15=17 pictures without numerical values.

The GIF ends with 15 blank frames added. So at the end it will show a blank for a while.

You can set the speed of the bar graph from the current position to the next position to make the transition smooth.

Divide the distance between the current position and the next position by the number of transition frames.

n_frames = 10
x = [1, 2, 3, 4, 5]
coordinates_lists = [[0, 0, 0, 0, 0],
                     [10, 30, 60, 30, 10],
                     [70, 40, 20, 40, 70],
                     [10, 20, 30, 40, 50],
                     [50, 40, 30, 20, 10],
                     [75, 0, 75, 0, 75],
                     [0, 0, 0, 0, 0]]
print('生成图表\n')
filenames = []
for index in np.arange(0, len(coordinates_lists) - 1):
    # 获取当前图像及下一图像的y轴坐标值
    y = coordinates_lists[index]
    y1 = coordinates_lists[index + 1]

    # 计算当前图像与下一图像y轴坐标差值
    y_path = np.array(y1) - np.array(y)
    for i in np.arange(0, n_frames + 1):
        # 分配每帧的y轴移动距离
        # 逐帧增加y轴的坐标值
        y_temp = (y + (y_path / n_frames) * i)
        # 绘制条形图
        plt.bar(x, y_temp)
        plt.ylim(0, 80)
        # 保存每一帧的图像
        filename = f'images/frame_{index}_{i}.png'
        filenames.append(filename)
        # 最后一帧重复,画面停留一会
        if (i == n_frames):
            for i in range(5):
                filenames.append(filename)
        # 保存图片
        plt.savefig(filename)
        plt.close()
print('保存图表\n')
# 生成GIF
print('生成GIF\n')
with imageio.get_writer('mybars.gif', mode='I') as writer:
    for filename in filenames:
        image = imageio.imread(filename)
        writer.append_data(image)
print('保存GIF\n')
print('删除图片\n')
# 删除图片
for filename in set(filenames):
    os.remove(filename)
print('完成')

It looks much smoother.

Well, let's change the configuration parameters related to the chart to make the chart look good.

n_frames = 10
bg_color = '#95A4AD'
bar_color = '#283F4E'
gif_name = 'bars'
x = [1, 2, 3, 4, 5]
coordinates_lists = [[0, 0, 0, 0, 0],
                     [10, 30, 60, 30, 10],
                     [70, 40, 20, 40, 70],
                     [10, 20, 30, 40, 50],
                     [50, 40, 30, 20, 10],
                     [75, 0, 75, 0, 75],
                     [0, 0, 0, 0, 0]]
print('生成图表\n')
filenames = []
for index in np.arange(0, len(coordinates_lists) - 1):
    y = coordinates_lists[index]
    y1 = coordinates_lists[index + 1]
    y_path = np.array(y1) - np.array(y)
    for i in np.arange(0, n_frames + 1):
        y_temp = (y + (y_path / n_frames) * i)
        # 绘制条形图
        fig, ax = plt.subplots(figsize=(8, 4))
        ax.set_facecolor(bg_color)
        plt.bar(x, y_temp, width=0.4, color=bar_color)
        plt.ylim(0, 80)
        # 移除图表的上边框和右边框
        ax.spines['right'].set_visible(False)
        ax.spines['top'].set_visible(False)
        # 设置虚线网格线
        ax.set_axisbelow(True)
        ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)
        # 保存每一帧的图像
        filename = f'images/frame_{index}_{i}.png'
        filenames.append(filename)

        # 最后一帧重复,画面停留一会
        if (i == n_frames):
            for i in range(5):
                filenames.append(filename)
        # 保存图片
        plt.savefig(filename, dpi=96, facecolor=bg_color)
        plt.close()
print('保存图表\n')
# 生成GIF
print('生成GIF\n')
with imageio.get_writer(f'{gif_name}.gif', mode='I') as writer:
    for filename in filenames:
        image = imageio.imread(filename)
        writer.append_data(image)
print('保存GIF\n')
print('删除图片\n')
# 删除图片
for filename in set(filenames):
    os.remove(filename)
print('完成')

Added background color to charts, colored bars, removed borders, added gridlines, and more.

It seems that the effect is not bad!

Of course there are some areas worth improving, such as adding titles. Interpolate to smooth out transitions and even move the bars on the x-axis.

Here you can do your own research.

03 Scatter plot

To draw a dynamic scatterplot, you need to consider both the x- and y-axis values.

It doesn't necessarily have to show the same number of dots on each frame here, so it needs to be corrected for the transition.

coordinates_lists = [[[0], [0]],
                     [[100, 200, 300], [100, 200, 300]],
                     [[400, 500, 600], [400, 500, 600]],
                     [[400, 500, 600, 400, 500, 600], [400, 500, 600, 600, 500, 400]],
                     [[500], [500]],
                     [[0], [0]]]
gif_name = 'movie'
n_frames = 10
bg_color = '#95A4AD'
marker_color = '#283F4E'
marker_size = 25
print('生成图表\n')
filenames = []
for index in np.arange(0, len(coordinates_lists) - 1):
    # 获取当前图像及下一图像的x与y轴坐标值
    x = coordinates_lists[index][0]
    y = coordinates_lists[index][1]
    x1 = coordinates_lists[index + 1][0]
    y1 = coordinates_lists[index + 1][1]
    # 查看两点差值
    while len(x) < len(x1):
        diff = len(x1) - len(x)
        x = x + x[:diff]
        y = y + y[:diff]
    while len(x1) < len(x):
        diff = len(x) - len(x1)
        x1 = x1 + x1[:diff]
        y1 = y1 + y1[:diff]
    # 计算路径
    x_path = np.array(x1) - np.array(x)
    y_path = np.array(y1) - np.array(y)
    for i in np.arange(0, n_frames + 1):
        # 计算当前位置
        x_temp = (x + (x_path / n_frames) * i)
        y_temp = (y + (y_path / n_frames) * i)
        # 绘制图表
        fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(aspect="equal"))
        ax.set_facecolor(bg_color)

        plt.scatter(x_temp, y_temp, c=marker_color, s=marker_size)
        plt.xlim(0, 1000)
        plt.ylim(0, 1000)
        # 移除边框线
        ax.spines['right'].set_visible(False)
        ax.spines['top'].set_visible(False)
        # 网格线
        ax.set_axisbelow(True)
        ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)
        ax.xaxis.grid(color='gray', linestyle='dashed', alpha=0.7)
        # 保存图片
        filename = f'images/frame_{index}_{i}.png'
        filenames.append(filename)
        if (i == n_frames):
            for i in range(5):
                filenames.append(filename)
        # 保存
        plt.savefig(filename, dpi=96, facecolor=bg_color)
        plt.close()
print('保存图表\n')
# 生成GIF
print('生成GIF\n')
with imageio.get_writer(f'{gif_name}.gif', mode='I') as writer:
    for filename in filenames:
        image = imageio.imread(filename)
        writer.append_data(image)
print('保存GIF\n')
print('删除图片\n')
# 删除图片
for filename in set(filenames):
    os.remove(filename)
print('完成')

The effect is as follows.

Of course there are more interesting scatter plot changes, such as letter changes.

Create a mask from an image using OpenCV, draw a plot filled with random x/y coordinates, and filter the points inside the mask. 

Use Matplotlib to draw scatter plots and ImageIO to generate gifs.

import os
import numpy as np
import matplotlib.pyplot as plt
import imageio
import random
import cv2


# 根据字母的形状, 将字母转化为多个随机点
def get_masked_data(letter, intensity=2):
    # 多个随机点填充字母
    random.seed(420)
    x = []
    y = []

    for i in range(intensity):
        x = x + random.sample(range(0, 1000), 500)
        y = y + random.sample(range(0, 1000), 500)

    if letter == ' ':
        return x, y

    # 获取图片的mask
    mask = cv2.imread(f'images/letters/{letter.upper()}.png', 0)
    mask = cv2.flip(mask, 0)

    # 检测点是否在mask中
    result_x = []
    result_y = []
    for i in range(len(x)):
        if (mask[y[i]][x[i]]) == 0:
            result_x.append(x[i])
            result_y.append(y[i])

    # 返回x,y
    return result_x, result_y


# 将文字切割成一个个字母
def text_to_data(txt, repeat=True, intensity=2):
    print('将文本转换为数据\n')
    letters = []
    for i in txt.upper():
        letters.append(get_masked_data(i, intensity=intensity))
    # 如果repeat为1时,重复第一个字母
    if repeat:
        letters.append(get_masked_data(txt[0], intensity=intensity))
    return letters


def build_gif(coordinates_lists, gif_name='movie', n_frames=10, bg_color='#95A4AD',
              marker_color='#283F4E', marker_size=25):
    print('生成图表\n')
    filenames = []
    for index in np.arange(0, len(coordinates_lists) - 1):
        # 获取当前图像及下一图像的x与y轴坐标值
        x = coordinates_lists[index][0]
        y = coordinates_lists[index][1]

        x1 = coordinates_lists[index + 1][0]
        y1 = coordinates_lists[index + 1][1]

        # 查看两点差值
        while len(x) < len(x1):
            diff = len(x1) - len(x)
            x = x + x[:diff]
            y = y + y[:diff]

        while len(x1) < len(x):
            diff = len(x) - len(x1)
            x1 = x1 + x1[:diff]
            y1 = y1 + y1[:diff]

        # 计算路径
        x_path = np.array(x1) - np.array(x)
        y_path = np.array(y1) - np.array(y)

        for i in np.arange(0, n_frames + 1):
            # 计算当前位置
            x_temp = (x + (x_path / n_frames) * i)
            y_temp = (y + (y_path / n_frames) * i)

            # 绘制图表
            fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(aspect="equal"))
            ax.set_facecolor(bg_color)
            plt.xticks([])  # 去掉x轴
            plt.yticks([])  # 去掉y轴
            plt.axis('off')  # 去掉坐标轴

            plt.scatter(x_temp, y_temp, c=marker_color, s=marker_size)

            plt.xlim(0, 1000)
            plt.ylim(0, 1000)

            # 移除框线
            ax.spines['right'].set_visible(False)
            ax.spines['top'].set_visible(False)

            # 网格线
            ax.set_axisbelow(True)
            ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)
            ax.xaxis.grid(color='gray', linestyle='dashed', alpha=0.7)

            # 保存图片
            filename = f'images/frame_{index}_{i}.png'

            if (i == n_frames):
                for i in range(5):
                    filenames.append(filename)

            filenames.append(filename)

            # 保存
            plt.savefig(filename, dpi=96, facecolor=bg_color)
            plt.close()
    print('保存图表\n')
    # 生成GIF
    print('生成GIF\n')
    with imageio.get_writer(f'{gif_name}.gif', mode='I') as writer:
        for filename in filenames:
            image = imageio.imread(filename)
            writer.append_data(image)
    print('保存GIF\n')
    print('删除图片\n')
    # 删除图片
    for filename in set(filenames):
        os.remove(filename)

    print('完成')


coordinates_lists = text_to_data('Python', repeat=True, intensity=50)

build_gif(coordinates_lists,
          gif_name='Python',
          n_frames=7,
          bg_color='#52A9F0',
          marker_color='#000000',
          marker_size=0.2)

Generate a dynamic scatterplot of the letters of a Python word.

Three main functions.

# 创建一个随机的x/y坐标列表,并使用mask对其进行过滤。
get_masked_data()
# 将文本转化为数据
text_to_data()
# 使用坐标点生成散点图, 保存GIF
build_gif()

Here the small F provides you with 26 letters, and you can combine them yourself.

Of course, other graphics are also possible, but you need to draw your own.

The size of the image should be 1000x1000 pixels, the mask shading is black, and the background is white.

Then save the png file in the images/letters folder and name it with a single character.

coordinates_lists = text_to_data('mac_', repeat=True, intensity=50)

build_gif(coordinates_lists,
          gif_name='mac',
          n_frames=7,
          bg_color='#F5B63F',
          marker_color='#000000',
          marker_size=0.2)

The result is as follows, the last one is a portrait.

Well, the sharing of this issue is over.

Using Matplotlib+Imageio to create dynamic charts, the case is relatively simple, you can download the code to learn by yourself.

Finally, reply " animation " on the official account to get the code and data used this time.

  end book  

This time, Xiao F and [Peking University Press] bring you 5 books related to big data.

" Massive data processing and big data technology combat " from principle to actual combat, on the basis of ensuring high concurrency, high availability, high scalability and high maintainability, teach you to build an offline batch processing based on massive data processing from scratch platform and online real-time computing platform and other big data systems. Click the image below to see details/purchase????

Book donation rules : After you like this article ("I'm watching" is not required), scan the QR code below and add Xiao F's WeChat. Send me the screenshots of the likes, and I will send you the lucky draw code until 21:00 on May 17th.

Thank you all for your support of F!

Recommended reading

···  END  ···

Guess you like

Origin blog.csdn.net/river_star1/article/details/116905957