This is the seventh day of my participation in the First Challenge 2022. For details: First Challenge 2022.

preface

Breathe soul into your data by drawing dynamic charts in Python!

Let’s have a good time

The development tools

Python version: 3.6.4

Related modules:

Numpy module;

Matplotlib module;

Imageio module;

And some modules that come with Python.

Environment set up

Install Python and add it to the environment variables. PIP installs the required related modules.

Today, we will introduce three kinds of dynamic chart drawing, line chart, bar chart, scatter chart.

The line chart

Let’s draw a simple line chart

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

# Generate 40 numbers between 30 and 40
y = np.random.randint(30.40, size=(40))
# Draw a polyline
plt.plot(y)
Set the minimum and maximum Y-axis values
plt.ylim(20.50)

# show
plt.show()
Copy the code

Create a list of random integers with values between 30 and 40 using Numpy

The list of integers is sliced below to generate a graph of the different stages

# First picture
plt.plot(y[:-3])
plt.ylim(20.50)
plt.savefig('1.png')
plt.show()

# second picture
plt.plot(y[:-2])
plt.ylim(20.50)
plt.savefig('2.png')
plt.show()

# 3
plt.plot(y[:-1])
plt.ylim(20.50)
plt.savefig('3.png')
plt.show()

# 4
plt.plot(y)
plt.ylim(20.50)
plt.savefig('4.png')
plt.show()
Copy the code

The x axis is 0:36, 0:37, 0:38, 0:39 four broken line graphs

With these four images, we can use Imageio to generate giFs

# generated 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)
Copy the code

Dynamic figure

I have a line graph that moves, but it doesn’t start at 0

filenames = []
num = 0
for i in y:
    num += 1
    Draw 40 line charts
    plt.plot(y[:num])
    plt.ylim(20.50)

    # Save the image file
    filename = f'{num}.png'
    filenames.append(filename)
    plt.savefig(filename)
    plt.close()

# generated GIF
with imageio.get_writer('mygif.gif', mode='I') as writer:
    for filename in filenames:
        image = imageio.imread(filename)
        writer.append_data(image)
Copy the code

Draw 40 line charts and save the images to generate GIFs

And you can see that the x-coordinate of the line graph goes from 0 to 40

The bar chart

The line chart above requires only one y value at a time, whereas the bar chart requires all y values so that all bars can move at the same time

Create fixed values for the X-axis and lists for the Y-axis, using Matplotlib’s bar chart function

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):
    # bar graph
    plt.bar(x, y)
    plt.ylim(0.80)

    # Save the image file
    filename = f'{index}.png'
    filenames.append(filename)

    # Repeat the last image for 15 frames (all 0) and 15 images
    if (index == len(coordinates_lists) - 1) :for i in range(15):
            filenames.append(filename)

    # save
    plt.savefig(filename)
    plt.close()

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

Delete 20 bar charts
for filename in set(filenames):
    os.remove(filename)
Copy the code

The number of bars is 5, and the number of bars is 2+15=17

GIF end segment, added 15 frames of blank image. So a period of blank space is displayed at the end

You can smooth out the transition by setting the speed at which the bar moves from one position to the next

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('Generate chart \n')
filenames = []
for index in np.arange(0.len(coordinates_lists) - 1) :Get the y coordinate of the current image and the next image
    y = coordinates_lists[index]
    y1 = coordinates_lists[index + 1]

    # Calculate the y coordinate difference between the current image and the next image
    y_path = np.array(y1) - np.array(y)
    for i in np.arange(0, n_frames + 1) :# Allocate the Y-axis movement distance per frame
        # Increase y coordinates frame by frame
        y_temp = (y + (y_path / n_frames) * i)
        # Draw a bar chart
        plt.bar(x, y_temp)
        plt.ylim(0.80)
        Save the image of each frame
        filename = f'images/frame_{index}_{i}.png'
        filenames.append(filename)
        # Repeat the last frame and stay for a while
        if (i == n_frames):
            for i in range(5):
                filenames.append(filename)
        # Save images
        plt.savefig(filename)
        plt.close()
print('Save chart \n')
# generated GIF
print('generated 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('save GIF \ n')
print('Delete picture \n')
# delete image
for filename in set(filenames):
    os.remove(filename)
print('complete')
Copy the code

It looks smooth

Let’s change the configuration parameters of the diagram to make it look nice

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('Generate chart \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)
        # Draw a bar chart
        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)
        Remove the top and right borders of the chart
        ax.spines['right'].set_visible(False)
        ax.spines['top'].set_visible(False)
        # Set dotted grid lines
        ax.set_axisbelow(True)
        ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)
        Save the image of each frame
        filename = f'images/frame_{index}_{i}.png'
        filenames.append(filename)

        # Repeat the last frame and stay for a while
        if (i == n_frames):
            for i in range(5):
                filenames.append(filename)
        # Save images
        plt.savefig(filename, dpi=96, facecolor=bg_color)
        plt.close()
print('Save chart \n')
# generated GIF
print('generated 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('save GIF \ n')
print('Delete picture \n')
# delete image
for filename in set(filenames):
    os.remove(filename)
print('complete')
Copy the code

Added background colors to charts, bar coloring, removing borders, adding grid lines, etc

A scatter diagram

To draw a dynamic scatter plot, you need to consider both X-axis and Y-axis values

It is not necessary to display the same number of points on each frame, so it needs to be corrected to make 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('Generate chart \n')
filenames = []
for index in np.arange(0.len(coordinates_lists) - 1) :Get the x and y coordinates of the current image and the next image
    x = coordinates_lists[index][0]
    y = coordinates_lists[index][1]
    x1 = coordinates_lists[index + 1] [0]
    y1 = coordinates_lists[index + 1] [1]
    # check the difference between two points
    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]
    # calculate path
    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) :# calculate the current position
        x_temp = (x + (x_path / n_frames) * i)
        y_temp = (y + (y_path / n_frames) * i)
        # Chart
        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)
        # Remove border lines
        ax.spines['right'].set_visible(False)
        ax.spines['top'].set_visible(False)
        # grid lines
        ax.set_axisbelow(True)
        ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)
        ax.xaxis.grid(color='gray', linestyle='dashed', alpha=0.7)
        # Save images
        filename = f'images/frame_{index}_{i}.png'
        filenames.append(filename)
        if (i == n_frames):
            for i in range(5):
                filenames.append(filename)
        # save
        plt.savefig(filename, dpi=96, facecolor=bg_color)
        plt.close()
print('Save chart \n')
# generated GIF
print('generated 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('save GIF \ n')
print('Delete picture \n')
# delete image
for filename in set(filenames):
    os.remove(filename)
print('complete')
Copy the code

The effect

Of course, there are more interesting scatter plot changes, like letter changes

Use OpenCV to create a mask from the image, draw a graph filled with random X/Y coordinates, and filter the points within the mask

Draw scatter diagrams using Matplotlib and generate GIFs using ImageIO

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


# Convert letters into random dots according to their shapes
def get_masked_data(letter, intensity=2) :
    # Multiple random dots to fill letters
    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

    Get the mask of the image
    mask = cv2.imread(f'images/letters/{letter.upper()}.png'.0)
    mask = cv2.flip(mask, 0)

    Check whether the point is in the 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])

    # returns the x, y
    return result_x, result_y


# Cut the text into letters
def text_to_data(txt, repeat=True, intensity=2) :
    print('Convert text to data \n')
    letters = []
    for i in txt.upper():
        letters.append(get_masked_data(i, intensity=intensity))
    # If repeat is 1, repeat the first letter
    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('Generate chart \n')
    filenames = []
    for index in np.arange(0.len(coordinates_lists) - 1) :Get the x and y coordinates of the current image and the next image
        x = coordinates_lists[index][0]
        y = coordinates_lists[index][1]

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

        # check the difference between two points
        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]

        # calculate path
        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) :# calculate the current position
            x_temp = (x + (x_path / n_frames) * i)
            y_temp = (y + (y_path / n_frames) * i)

            # Chart
            fig, ax = plt.subplots(figsize=(6.6), subplot_kw=dict(aspect="equal"))
            ax.set_facecolor(bg_color)
            plt.xticks([])  Get rid of the X-axis
            plt.yticks([])  # get rid of the Y-axis
            plt.axis('off')  # Drop the axes

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

            plt.xlim(0.1000)
            plt.ylim(0.1000)

            # Remove the frame line
            ax.spines['right'].set_visible(False)
            ax.spines['top'].set_visible(False)

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

            # Save images
            filename = f'images/frame_{index}_{i}.png'

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

            filenames.append(filename)

            # save
            plt.savefig(filename, dpi=96, facecolor=bg_color)
            plt.close()
    print('Save chart \n')
    # generated GIF
    print('generated 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('save GIF \ n')
    print('Delete picture \n')
    # delete image
    for filename in set(filenames):
        os.remove(filename)

    print('complete')


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)
Copy the code

Generates a dynamic scatter diagram of Python word letters

Three main functions

Create a random x/y coordinate list and filter it with mask.
get_masked_data()
# Convert text to data
text_to_data()
# Generate scatter graph with coordinate points, save GIF
build_gif()
Copy the code

There are 26 letters available for everyone to compose

Of course, other graphics are also possible, is the need to draw their own

The image should be 1000×1000 pixels in size with a black mask color and a white background

Then save the PNG file in the images/ Letters folder with a single character name

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)
Copy the code

Here’s the result. The last one is a figure