Pygal: Change dot type/ symbol - pygal

I want to change the dots in my pygal chart from the default circles to rectangles (sounds weird but makes sense in my case) and be able to define the size of the rectangles. I couldn't find a solution in the docs. With the config module I can show/ hide the dots and change the dots size but as far as I can see I can't change the dot icon. I also coulndn't find a solution in the style module.
Is there an easy way to do it?
Thanks a lot

There's no way to achieve this using styles or configuration: the circular dots are hard-coded into the function that renders line charts. But, you can easily extend the line chart class and override this function to create a chart with any shape of dot.
If you view the source code of the Line class you will see the following code in the line function:
alter(
self.svg.transposable_node(
dots,
'circle',
cx=x,
cy=y,
r=serie.dots_size,
class_='dot reactive tooltip-trigger'
), metadata
)
This creates a circle for each dot and adds it to the SVG data that will be used to generate the chart.
Copy the whole function into your new class and replace those lines with the following code. This will add squares instead of circles, using the dots_size configuration to determine the width and height:
alter(
self.svg.transposable_node(
dots,
'rect',
x=x - serie.dots_size / 2,
y=y - serie.dots_size / 2,
width=serie.dots_size,
height=serie.dots_size,
class_='dot reactive tooltip-trigger'
), metadata
)
The complete class would look something like this (it looks like a lot of code, but most of it is copy-pasted):
from pygal.util import alter, decorate
class SquareDots(pygal.Line):
def __init__(self, *args, **kwargs):
super(SquareDots, self).__init__(*args, **kwargs)
def line(self, serie, rescale=False):
serie_node = self.svg.serie(serie)
if rescale and self.secondary_series:
points = self._rescale(serie.points)
else:
points = serie.points
view_values = list(map(self.view, points))
if serie.show_dots:
for i, (x, y) in enumerate(view_values):
if None in (x, y):
continue
if self.logarithmic:
if points[i][1] is None or points[i][1] <= 0:
continue
if (serie.show_only_major_dots and self.x_labels
and i < len(self.x_labels)
and self.x_labels[i] not in self._x_labels_major):
continue
metadata = serie.metadata.get(i)
classes = []
if x > self.view.width / 2:
classes.append('left')
if y > self.view.height / 2:
classes.append('top')
classes = ' '.join(classes)
self._confidence_interval(
serie_node['overlay'], x, y, serie.values[i], metadata
)
dots = decorate(
self.svg,
self.svg.node(serie_node['overlay'], class_="dots"),
metadata
)
val = self._format(serie, i)
# This is the part that needs to be changed.
alter(self.svg.transposable_node(
dots,
'rect',
x=x - serie.dots_size / 2,
y=y - serie.dots_size / 2,
width=serie.dots_size,
height=serie.dots_size,
class_='dot reactive tooltip-trigger'
), metadata
)
self._tooltip_data(
dots, val, x, y, xlabel=self._get_x_label(i)
)
self._static_value(
serie_node, val, x + self.style.value_font_size,
y + self.style.value_font_size, metadata
)
if serie.stroke:
if self.interpolate:
points = serie.interpolated
if rescale and self.secondary_series:
points = self._rescale(points)
view_values = list(map(self.view, points))
if serie.fill:
view_values = self._fill(view_values)
if serie.allow_interruptions:
sequences = []
cur_sequence = []
for x, y in view_values:
if y is None and len(cur_sequence) > 0:
sequences.append(cur_sequence)
cur_sequence = []
elif y is None:
continue
else:
cur_sequence.append((x, y))
if len(cur_sequence) > 0:
sequences.append(cur_sequence)
else:
sequences = [view_values]
if self.logarithmic:
for seq in sequences:
for ele in seq[::-1]:
y = points[seq.index(ele)][1]
if y is None or y <= 0:
del seq[seq.index(ele)]
for seq in sequences:
self.svg.line(
serie_node['plot'],
seq,
close=self._self_close,
class_='line reactive' +
(' nofill' if not serie.fill else '')
)
Your new class can then be used like any other pygal chart.
chart = SquareDots(dots_size=50)
chart.add("line", [1, 2, 3, 4, 3, 2])
chart.render_to_png("chart.png")

Related

How to FadeIn mulitple Complex Value Tracker Points in Manim?

I'm expecting individual points to FadeIn at their respective locations, but it appears they all aggregate at the last tracker point.
class Complex_Dots_with_Labels(Scene):
def construct(self):
def label_value_and_position(mobject):
position_string = "{0:.1f}{1}{2:.1f}i".format(tracker.get_center()[0],
'+-'[tracker.get_center()[1] < 0],
abs(tracker.get_center()[1]))
mobject.set(submobjects = [SingleStringMathTex(position_string)])
mobject.next_to(tracker)
plane = ComplexPlane().add_coordinates()
tracker = [ComplexValueTracker(np.sign(r)*(2*r**2)**(1/2)*np.exp(np.pi/4*1j))
for r in np.arange(-2,2.5,0.5)
if abs(r) != 0.0]
dots = VGroup(*[Dot(color=WHITE, radius=0.1, name = 'Dot_{}'.format(i)).add_updater(lambda x: x.move_to(tracker[i].points))
for i in range(len(tracker))])
anims = []
for d in dots:
anims.append(FadeIn(d, scale = 2))
self.play(Write(plane))
self.play(*anims)
self.wait(1)
How to place the dots at their specified locations?
Video is attached here

Custom data loader for images and labels in pytorch. Difficulty with inputs in loss function

I was working on this https://www.kaggle.com/gti-upm/leapgestrecog data set lately. Its a hand gesture dataset and I was trying to make a classifier. Due to images available in different types of folder I made my on data loader. Here it is
class DatasetLoader(Dataset):
def __init__(self,path):
self.path_list = path
self.labels = []
self.to_tensor = transforms.ToTensor()
self.resize = transforms.Resize((120,320))
self.gray = transforms.Grayscale(num_output_channels = 1)
self._init_dataset()
def _init_dataset(self):
labels = set()
for diro in os.listdir("/kaggle/input/leapgestrecog/leapGestRecog"):
for d in os.listdir(os.path.join("/kaggle/input/leapgestrecog/leapGestRecog",diro)):
if len(d.split('_'))>2:
labels.add("_".join(d.split("_")[-2:]))
else:
labels.add(d.split("_")[-1])
labels = list(labels)
## help me on this line with some codes
def __getitem__(self,idx):
if torch.is_tensor(idx):
idx = idx.tolist()
img_name = self.path_list[idx]
img = Image.open(img_name)
img = self.resize(img)
img = self.gray(img)
img = self.to_tensor(img)
if len(img_name.split('/')[-2].split('_')) > 2:
label = "_".join(img_name.split('/')[-2].split('_')[-2:])
else:
label = img_name.split('/')[-2].split('_')[-1]
label = ## Here also
return img,label
def __len__(self):
return len(self.path_list)
I have problem with label which I am getting from this dataset loader. As I have created a model which takes n batches of data with 10 classes so during loss calculation I need my labels to of size(n,10). I dont know what to do. Here is my network design:
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = nn.Conv2d(1,32,5)
self.pool = nn.MaxPool2d(2,2)
self.conv2 = nn.Conv2d(32,64,3)
self.conv3 = nn.Conv2d(64,64,3)
self.fc1 = nn.Linear(64*38*13,128)
self.fc2 = nn.Linear(128,10)
def forward(self,x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = self.pool(F.relu(self.conv3(x)))
x = x.view(64,64*38*13)
x = F.relu(self.fc1(x))
return F.log_softmax(self.fc2(x),dim = 1)
If y is label of an image. To train our network we feed loss function with y and output. But output we get is of size (64,10) , so I need help with the label in dataloader
I see that you have a bit of misunderstanding about the input dimensions to a multi-class loss function in PyTorch. The most commonly used loss function for classification problems is nn.CrossEntropyLoss(), which expects raw logits of size (n, c) (e.g. (64, 10)) as input1, and target (ground truth label) of size (n) (e.g. (10)).
So instead of doing return F.log_softmax(self.fc2(x),dim = 1), it would be more stable to directly do return x and use CrossEntropyLoss. There is no need to reshape your labels, you can directly calculate the loss by doing something like this:
criterion = nn.CrossEntropyLoss()
# Let x be (64, 10) output from model
# Let y be (10,) label
loss = criterion(x, y)

Pygame window doesn't show on Mac OS Catalina

Running:
MacOS Catalina 10.15.3
Python 3.7.6.
Pygame 1.9.6
I just started programming and I am trying to run a reinforcement learning Pygame code (link: https://github.com/harvitronix/reinforcement-learning-car). When I run python3.7 -m pygame.examples.aliens I see the test window + sound and everything works.
However when I try to run the code for the game I am trying to get working I at first only saw the loading wheel, I fixed the loading wheel by putting in the following loop. `
pygame.display.update()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
When I try to run it now, I only see a black pygame window pop-up, so no loading wheel but also not the game, it also seems like the game doesn't run in the background (this was the case without the above loop). See the complete original code below:
import random
import math
import numpy as np
import pygame
from pygame.color import THECOLORS
import sys
import pymunk
from pymunk.vec2d import Vec2d
from pymunk.pygame_util import draw
# PyGame init
width = 1000
height = 700
pygame.init()
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
# Turn off alpha since we don't use it.
screen.set_alpha(None)
# Showing sensors and redrawing slows things down.
show_sensors = True
draw_screen = True
class GameState:
def __init__(self):
# Global-ish.
self.crashed = False
# Physics stuff.
self.space = pymunk.Space()
self.space.gravity = pymunk.Vec2d(0., 0.)
# Create the car.
self.create_car(100, 100, 0.5)
# Record steps.
self.num_steps = 0
# Create walls.
static = [
pymunk.Segment(
self.space.static_body,
(0, 1), (0, height), 1),
pymunk.Segment(
self.space.static_body,
(1, height), (width, height), 1),
pymunk.Segment(
self.space.static_body,
(width-1, height), (width-1, 1), 1),
pymunk.Segment(
self.space.static_body,
(1, 1), (width, 1), 1)
]
for s in static:
s.friction = 1.
s.group = 1
s.collision_type = 1
s.color = THECOLORS['red']
self.space.add(static)
# Create some obstacles, semi-randomly.
# We'll create three and they'll move around to prevent over-fitting.
self.obstacles = []
self.obstacles.append(self.create_obstacle(200, 350, 100))
self.obstacles.append(self.create_obstacle(700, 200, 125))
self.obstacles.append(self.create_obstacle(600, 600, 35))
# Create a cat.
self.create_cat()
def create_obstacle(self, x, y, r):
c_body = pymunk.Body(pymunk.inf, pymunk.inf)
c_shape = pymunk.Circle(c_body, r)
c_shape.elasticity = 1.0
c_body.position = x, y
c_shape.color = THECOLORS["blue"]
self.space.add(c_body, c_shape)
return c_body
def create_cat(self):
inertia = pymunk.moment_for_circle(1, 0, 14, (0, 0))
self.cat_body = pymunk.Body(1, inertia)
self.cat_body.position = 50, height - 100
self.cat_shape = pymunk.Circle(self.cat_body, 30)
self.cat_shape.color = THECOLORS["orange"]
self.cat_shape.elasticity = 1.0
self.cat_shape.angle = 0.5
direction = Vec2d(1, 0).rotated(self.cat_body.angle)
self.space.add(self.cat_body, self.cat_shape)
def create_car(self, x, y, r):
inertia = pymunk.moment_for_circle(1, 0, 14, (0, 0))
self.car_body = pymunk.Body(1, inertia)
self.car_body.position = x, y
self.car_shape = pymunk.Circle(self.car_body, 25)
self.car_shape.color = THECOLORS["green"]
self.car_shape.elasticity = 1.0
self.car_body.angle = r
driving_direction = Vec2d(1, 0).rotated(self.car_body.angle)
self.car_body.apply_impulse(driving_direction)
self.space.add(self.car_body, self.car_shape)
def frame_step(self, action):
if action == 0: # Turn left.
self.car_body.angle -= .2
elif action == 1: # Turn right.
self.car_body.angle += .2
# Move obstacles.
if self.num_steps % 100 == 0:
self.move_obstacles()
# Move cat.
if self.num_steps % 5 == 0:
self.move_cat()
driving_direction = Vec2d(1, 0).rotated(self.car_body.angle)
self.car_body.velocity = 100 * driving_direction
# Update the screen and stuff.
screen.fill(THECOLORS["black"])
draw(screen, self.space)
self.space.step(1./10)
if draw_screen:
pygame.display.flip()
clock.tick()
# Get the current location and the readings there.
x, y = self.car_body.position
readings = self.get_sonar_readings(x, y, self.car_body.angle)
normalized_readings = [(x-20.0)/20.0 for x in readings]
state = np.array([normalized_readings])
# Set the reward.
# Car crashed when any reading == 1
if self.car_is_crashed(readings):
self.crashed = True
reward = -500
self.recover_from_crash(driving_direction)
else:
# Higher readings are better, so return the sum.
reward = -5 + int(self.sum_readings(readings) / 10)
self.num_steps += 1
return reward, state
def move_obstacles(self):
# Randomly move obstacles around.
for obstacle in self.obstacles:
speed = random.randint(1, 5)
direction = Vec2d(1, 0).rotated(self.car_body.angle + random.randint(-2, 2))
obstacle.velocity = speed * direction
def move_cat(self):
speed = random.randint(20, 200)
self.cat_body.angle -= random.randint(-1, 1)
direction = Vec2d(1, 0).rotated(self.cat_body.angle)
self.cat_body.velocity = speed * direction
def car_is_crashed(self, readings):
if readings[0] == 1 or readings[1] == 1 or readings[2] == 1:
return True
else:
return False
def recover_from_crash(self, driving_direction):
"""
We hit something, so recover.
"""
while self.crashed:
# Go backwards.
self.car_body.velocity = -100 * driving_direction
self.crashed = False
for i in range(10):
self.car_body.angle += .2 # Turn a little.
screen.fill(THECOLORS["grey7"]) # Red is scary!
draw(screen, self.space)
self.space.step(1./10)
if draw_screen:
pygame.display.flip()
clock.tick()
def sum_readings(self, readings):
"""Sum the number of non-zero readings."""
tot = 0
for i in readings:
tot += i
return tot
def get_sonar_readings(self, x, y, angle):
readings = []
"""
Instead of using a grid of boolean(ish) sensors, sonar readings
simply return N "distance" readings, one for each sonar
we're simulating. The distance is a count of the first non-zero
reading starting at the object. For instance, if the fifth sensor
in a sonar "arm" is non-zero, then that arm returns a distance of 5.
"""
# Make our arms.
arm_left = self.make_sonar_arm(x, y)
arm_middle = arm_left
arm_right = arm_left
# Rotate them and get readings.
readings.append(self.get_arm_distance(arm_left, x, y, angle, 0.75))
readings.append(self.get_arm_distance(arm_middle, x, y, angle, 0))
readings.append(self.get_arm_distance(arm_right, x, y, angle, -0.75))
if show_sensors:
pygame.display.update()
return readings
def get_arm_distance(self, arm, x, y, angle, offset):
# Used to count the distance.
i = 0
# Look at each point and see if we've hit something.
for point in arm:
i += 1
# Move the point to the right spot.
rotated_p = self.get_rotated_point(
x, y, point[0], point[1], angle + offset
)
# Check if we've hit something. Return the current i (distance)
# if we did.
if rotated_p[0] <= 0 or rotated_p[1] <= 0 \
or rotated_p[0] >= width or rotated_p[1] >= height:
return i # Sensor is off the screen.
else:
obs = screen.get_at(rotated_p)
if self.get_track_or_not(obs) != 0:
return i
if show_sensors:
pygame.draw.circle(screen, (255, 255, 255), (rotated_p), 2)
# Return the distance for the arm.
return i
def make_sonar_arm(self, x, y):
spread = 10 # Default spread.
distance = 20 # Gap before first sensor.
arm_points = []
# Make an arm. We build it flat because we'll rotate it about the
# center later.
for i in range(1, 40):
arm_points.append((distance + x + (spread * i), y))
return arm_points
def get_rotated_point(self, x_1, y_1, x_2, y_2, radians):
# Rotate x_2, y_2 around x_1, y_1 by angle.
x_change = (x_2 - x_1) * math.cos(radians) + \
(y_2 - y_1) * math.sin(radians)
y_change = (y_1 - y_2) * math.cos(radians) - \
(x_1 - x_2) * math.sin(radians)
new_x = x_change + x_1
new_y = height - (y_change + y_1)
return int(new_x), int(new_y)
def get_track_or_not(self, reading):
if reading == THECOLORS['black']:
return 0
else:
return 1
if __name__ == "__main__":
game_state = GameState()
while True:
game_state.frame_step((random.randint(0, 2)))
I don't think the issue is with my python version because the test runs normal. Anybody see the issue?
Thanks!
Problem solved. I put the loop in the beginning of the code but it should have gone beneath:
if draw_screen:
pygame.display.flip()

Constrain the drag of rectangular ROI (Matlab)

I have a simple program which replaces a selected region of one image with the corresponding region in another image. I am trying to use imrect() in conjunction with makeConstrainToRectFcn to select a rectangular ROI which cannot be extended beyond the boundaries of the image.
However, when I run the code, the ROI can initially be drawn to include the areas outside the image frame. This leads to the error: Index exceeds matrix dimensions.
Is there any way that the rectangle cannot be drawn outside the image from the outset? Alternatively, is it possible to ensure that the operation does not execute unless the rectangle is constrained within the axes limits?
Any suggestions would be greatly appreciated.
My code:
% Sample images:
X=imread('office_1.jpg');
Y=imread('office_5.jpg');
figure, imshow(X)
h = imrect;
api = iptgetapi(h);
fcn = makeConstrainToRectFcn('imrect',get(gca,'XLim'),...
get(gca,'YLim'));
api.setPositionConstraintFcn(fcn);
wait(h);
rect = getPosition(h);
x1 =rect(1);
x2 = x1 + rect(3);
y1 =rect(2);
y2 = y1 + rect(4);
Z = X; % Initialize
Z(y1:y2, x1:x2, :) = Y(y1:y2, x1:x2, :);
imshow(Z)
This should do the job:
% Sample images:
X = imread('office_1.jpg');
Y = imread('office_5.jpg');
% Show image X:
figure, imshow(X);
% Define the ROI constraint:
h = imrect();
h.setPositionConstraintFcn(#(p) roi_constraint(p,size(X)));
% Wait for the ROI to be confirmed:
roi = round(wait(h));
x1 = roi(1);
x2 = x1 + roi(3);
y1 = roi(2);
y2 = y1 + roi(4);
% Create the final image Z and display it:
Z = X;
Z(y1:y2,x1:x2,:) = Y(y1:y2,x1:x2,:);
imshow(Z);
% Auxiliary function for ROI constraint:
function p_adj = roi_constraint(p,img_size)
p_adj(1) = max([1 p(1)]);
p_adj(2) = max([1 p(2)]);
p_adj(3) = min([(img_size(2) - 1) p(3)]);
p_adj(4) = min([(img_size(1) - 1) p(4)]);
end
The script has been tested under Matlab 2017a and works as expected. As you can see, the main difference is the way the size constraint is being handled: in your case, it wasn't properly applied before wait was hit, thus returning an invalid rectangle. Also, in order to avoid a wrong offsetting, the round function has been applied to the rectangle.

How do I modify my matplotlib FuncAnimation init_func to remove artifacts that were introduced by using twinx?

Summary: I created a script for drawing strip charts with the matplotlib animation framework to support multiple y-axes using the Axes.twinx(), which resulted in an artifact on the plot which I can't remove. I think I need to modify the animation init_func to account for the multiple axes.
Python 2.7, matplotlib 2.0.2 with the Qt backend, and conda 4.3.18, running on Ubuntu Linux 17.04.
Full Description: I created strip chart drawing program based on the matplotlib strip chart demo:
https://matplotlib.org/examples/animation/strip_chart_demo.html
I modified it to help some EE's on a project including rewriting it in a more procedural style so they were more comfortable working with it (not sure how successful that was, we're still talking about it), adding support for multiple lines, and changed it so it was continuously scrolling left.
The script includes a makeChart() function that creates a series of Line2D's in a loop, and adds them to a list, and returns them to the caller:
lines = []
for iline in range(0,linesPerPlot):
lines.append(makeLine(ax, maxt, dt, ymin, ymax, colors[iline % len(colors)]))
return lines
When I added multiple lines there was an artifact that appeared when running with blit=True that I got rid of by adding an init_function.
The init function is simple and looks like this:
def initDisplay(lines):
"""Init display."""
return lines
When the script executes makeChart() is called and returns a list of Line2D's that are being plotted, it creates a lambda that wraps the init function that is then passed to FuncAnimation:
lines = makeChart(ax, secondsPerPlot, secondsPerSample, linesPerPlot, ymin, ymax)
...
init = lambda: initDisplay(lines)
ani = animation.FuncAnimation(fig, update, emitter, init_func=init, interval=millisPerFrame, blit=True)
The result works reasonable well:
And an example with that displays some generated sin waves is here:
https://gist.github.com/mdkrajnak/f7cfd3f720453d53da4a80fa45df3b66
Later I made an additional modification so that each line had an independent y-axis by using Axes.twinx. After the modification there's now an artifact that I cannot remove that appears to be left over from the first time the first line is rendered.
The new inner loop in makeChart() looks like:
lines = []
lines.append(makeLine(ax, maxt, dt, ymin, ymax, colors[0]))
for iline in range(1,linesPerPlot):
twin_ax = ax.twinx()
lines.append(makeLine(twin_ax, maxt, dt, ymin, ymax, colors[iline % len(colors)]))
return lines
And the full code is here:
https://gist.github.com/mdkrajnak/e8b37300545f3ffea651d628933bd0ee
I tried modifying the init function so that it returned a list with both the lines and axes:
def initDisplay(lines, axs):
"""Init display."""
return lines + axs
And the makeChart() function so it returned the axes along with the lines in the sequence of artists that it returned:
lines = []
axs = []
# Add first line, then add subsequent lines sharing the x-axis.
lines.append(makeLine(ax, maxt, dt, ymin, ymax, colors[0]))
axs.append(ax)
for iline in range(1,linesPerPlot):
twin_ax = ax.twinx()
lines.append(makeLine(twin_ax, maxt, dt, ymin, ymax, colors[iline % len(colors)]))
axs.append(twin_ax)
return lines, axs
The full code is here:
https://gist.github.com/mdkrajnak/e6eaca509cd8321b9b56a4d25c3e1e80
But this version fails with "AttributeError: draw_artist can only be used after an initial draw which caches the render"
File "/home/mdk/opt/miniconda3/envs/p2/lib/python2.7/site-packages/matplotlib/animation.py", line 1123, in _post_draw self._blit_draw(self._drawn_artists, self._blit_cache)
File "/home/mdk/opt/miniconda3/envs/p2/lib/python2.7/site-packages/matplotlib/animation.py", line 1138, in _blit_draw a.axes.draw_artist(a)
File "/home/mdk/opt/miniconda3/envs/p2/lib/python2.7/site-packages/matplotlib/axes/_base.py", line 2441, in draw_artist raise AttributeError(msg)
AttributeError: draw_artist can only be used after an initial draw which caches the render
My thinking is still that the init function needs to return the axes along with the lines, but I need to somehow cause an initial draw of the axes before the init function is called. Is there something I can to to prerender the axes, or is there something else I need to do?
It seems that blitting is performed per axes. So it might be that the procedure is
for ax in all axes:
get axes background
draw line
This means that the first line is part of the background from the second axes and as such will be part of every successive frame.
The only solution I can think of at the moment is to make the lines invisible until the backgrounds of all axes have been stored for blitting.
line = Line2D(tdata, ydata, color=color, visible=False)
Only after the first call to updateLines turn them visible again.
n = [0]
def updateLines(lines, arrays):
"""Update individual lines and return a sequence of artists to the animator."""
artists = []
for iline in range(len(lines)):
artists.append(updateLine(lines[iline], arrays[iline]))
if n[0] > 0:
lines[iline].set_visible(True)
n[0] += 1
return artists
Complete code:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.lines import Line2D
import math
# Initalize script constants
ymin = -1.1
ymax = 1.1
linesPerPlot = 3
samplesPerFrame = 1
framesPerSecond = 20
secondsPerPlot = 5
# Calculate dependent constants
samplesPerSecond = samplesPerFrame * framesPerSecond
samplesPerPlot = samplesPerSecond * secondsPerPlot
secondsPerSample = 1.0/samplesPerSecond
millisPerFrame = 1000.0/framesPerSecond
# Define core functions
def makeLine(ax, maxt, dt, ymin, ymax, color):
"""Make an empty Line2D for the initial chart."""
nvalues = int(round(maxt/dt))
tdata = [dt*tm for tm in range(nvalues)]
ydata = [0 for tm in range(nvalues)]
line = Line2D(tdata, ydata, color=color, visible=False) ### <- visible false
ax.add_line(line)
ax.set_ylim(ymin, ymax)
return line
def makeChart(ax, maxt, dt, linesPerPlot, ymin, ymax):
"""Make a chart and return a list of the lines it contains."""
colors = [ 'r', 'b', 'g', 'k' ]
lines = []
# Add first line, then add subsequent lines sharing the x-axis.
lines.append(makeLine(ax, maxt, dt, ymin, ymax, colors[0]))
for iline in range(1,linesPerPlot):
twin_ax = ax.twinx()
lines.append(makeLine(twin_ax, maxt, dt, ymin, ymax, colors[iline % len(colors)]))
ax.set_xlim(0, maxt)
return lines
def initDisplay(lines):
"""Init display."""
return lines
def updateLine(line, ys):
"""Update the data in one line, popping off the last value."""
tdata, ydata = line.get_data()
for y in ys:
ydata.append(y)
ydata.pop(0)
line.set_data(tdata, ydata)
return line
n = [0]
def updateLines(lines, arrays):
"""Update individual lines and return a sequence of artists to the animator."""
artists = []
for iline in range(len(lines)):
artists.append(updateLine(lines[iline], arrays[iline]))
if n[0] > 0:
lines[iline].set_visible(True)
n[0] += 1
return artists
def emitData(linesPerPlot, samplesPerFrame):
"""Create the data that will be plotted."""
nsample = 0
while True:
samples = [[] for i in range(linesPerPlot)]
for isample in range(samplesPerFrame):
nsample = nsample + 1
for iline in range(linesPerPlot):
pi_increment = (math.pi/(10.0 * (iline+1)))
samples[iline].append(math.sin(nsample * pi_increment))
yield samples
# Make chart.
fig, ax = plt.subplots()
lines = makeChart(ax, secondsPerPlot, secondsPerSample, linesPerPlot, ymin, ymax)
# Start the animator.
update = lambda samples: updateLines(lines, samples)
emitter = lambda: emitData(linesPerPlot, samplesPerFrame)
init = lambda: initDisplay(lines)
ani = animation.FuncAnimation(fig, update, emitter, init_func=init, interval=millisPerFrame, blit=True)
plt.show()

Resources