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