Home > Mobile >  Python and Matplotlib, how to add multiple window? [duplicate]
Python and Matplotlib, how to add multiple window? [duplicate]

Time:09-27

"""
Description: The goal is to model a population of people who are infected with a virus.
"""

# TODO: Make a curve for each case so we can see in real time how much the virus is spreading.

# -------------------- IMPORTS --------------------


from random import randint, random
from matplotlib import pyplot as plt, animation as anim
import math


# --------------------  GLOBAL VARIABLES --------------------


number_of_dots = 100  # number of dots to generate
shape = "o"  # tip: use '.' instead if you put a value < 3 in minimal_distance

HEIGHT_WIDTH = 100  # Window height and width (yes, window shape must be a square)
BORDER_MIN = 1  # minimum distance from the border
BORDER_MAX = HEIGHT_WIDTH - 1  # maximum distance from the border

minimal_distance = 3  # Minimal distance at initialization and for contamination
time = 0  # Time to initialize
time_step = 0.1  # Time step for the simulation

transmission_rate = 0.7  # Chance of a dot to be infected
time_to_cure = 40  # Time to cure a dot
time_before_being_contagious_again = 40  # Time before being contagious again
virus_mortality = 0.0005  # Chance of a dot to die per tick


# -------------------- CLASSES & METHODS --------------------


class Dot:
    def __init__(self, x: int, y: int):
        """Constructor for the Dot class

        Args:
            x (int): abscissa of the dot
            y (int): ordinate of the dot
        """
        self.x = x
        self.y = y
        self.velx = (random() - 0.5) / 5
        self.vely = (random() - 0.5) / 5
        self.is_infected = False
        self.infected_at = -1
        self.has_been_infected = False
        self.cured_at = -1

    def init_checker(x: int, y: int, already_used_coords: list):
        """Checks if the dot is in a distance of a minimal_distance from another dot

        Args:
            x (int): absissa of the dot
            y (int): ordinate of the dot
            already_used_coords (list): list of already occupated coordinates (by initialized dots)

        Returns:
            boolean: Whether the Dot should be initialized or not
        """
        for coord in already_used_coords:
            if Dot.get_distance(coord[0], x, coord[1], y) < minimal_distance:
                return False
        return True

    def get_distance(x1: float, y1: float, x2: float, y2: float):
        """Gets the distance between two Dot objects

        Args:
            x1 (float): abscissa of the first dot
            y1 (float): ordinate of the first dot
            x2 (float): abscissa of the second dot
            y2 (float): ordinate of the second dot

        Returns:
            float: distance between the two dots
        """
        return math.sqrt((x2 - x1) ** 2   (y2 - y1) ** 2)

    def initalize_multiple_dots():
        """Generates a list of Dots

        Returns:
            list: initialized dots
        """
        dots = []
        already_used_coords = []

        while len(dots) != number_of_dots:
            randx = randint(BORDER_MIN, BORDER_MAX)
            randy = randint(BORDER_MIN, BORDER_MAX)
            # So the dots keep distances between each other
            if Dot.init_checker(randx, randy, already_used_coords):
                dot = Dot(randx, randy)
                already_used_coords.append((randx, randy))
            else:
                continue
            dots.append(dot)
        print("There are", len(dots), "dots in the area.")
        return dots

    def move(self):
        """Moves the dot and makes sure they don't go out of the area or touch each other. They've 4% chance to change direction."""
        global dots, dead_dots

        if random() < 0.96:
            self.x = self.x   self.velx
            self.y = self.y   self.vely

        else:
            self.x = self.x   self.velx
            self.y = self.y   self.vely
            # Change 2 to lower value to make the dots go faster
            self.velx = (random() - 0.5) / (2 / (time_step   1))
            # Change 2 to lower value to make the dots go faster
            self.vely = (random() - 0.5) / (2 / (time_step   1))

        if self.x >= BORDER_MAX:
            self.x = BORDER_MAX
            self.velx = -1 * self.velx

        if self.x <= BORDER_MIN:
            self.x = BORDER_MIN
            self.velx = -1 * self.velx

        if self.y >= BORDER_MAX:
            self.y = BORDER_MAX
            self.vely = -1 * self.vely

        if self.y <= BORDER_MIN:
            self.y = BORDER_MIN
            self.vely = -1 * self.vely

        if (
            random() < transmission_rate
            and not self.has_been_infected
            and not self.is_infected
        ):
            for dot in dots:
                if (
                    dot.is_infected
                    and Dot.get_distance(self.x, self.y, dot.x, dot.y)
                    < minimal_distance
                ):
                    self.is_infected = True
                    self.infected_at = time
                    break
        
        if self.is_infected and random() < virus_mortality:
            dead_dots.append(self)
            dots.remove(self)

    def move_all(dots: list):
        """Moves a given list of dots. Make sur that infected dots goes in the correct axes

        Args:
            dots (list): List of Dot objects
        """
        global healthy_dots, infected_dots, cured_dots, time
        for dot in dots:
            dot.move()
            if (
                dot.is_infected
                and dot.infected_at != -1
                and dot.infected_at   time_to_cure < time
            ):
                dot.is_infected = False
                dot.has_been_infected = True
                dot.cured_at = time

            if (
                dot.has_been_infected
                and dot.cured_at != -1
                and dot.cured_at   time_before_being_contagious_again < time
            ):
                dot.has_been_infected = False
                dot.infected_at = -1
                dot.cured_at = -1

        healthy_dots.set_data(
            [
                dot.x
                for dot in dots
                if not dot.is_infected and not dot.has_been_infected
            ],
            [
                dot.y
                for dot in dots
                if not dot.is_infected and not dot.has_been_infected
            ],
        )

        infected_dots.set_data(
            [dot.x for dot in dots if dot.is_infected],
            [dot.y for dot in dots if dot.is_infected],
        )

        cured_dots.set_data(
            [dot.x for dot in dots if dot.has_been_infected],
            [dot.y for dot in dots if dot.has_been_infected],
        )

        time  = time_step
        plt.title(
            f"Healthy: {len([dot for dot in dots if not dot.is_infected and not dot.has_been_infected])}"
              f"   |   Infected: {len([dot for dot in dots if dot.is_infected])}"
              f"   |   Cured: {len([dot for dot in dots if dot.has_been_infected])}"
              f"   |   Dead: {len([dot for dot in dead_dots])}",
            color="black",
        )

        
        def updateAxes():
            """Updates axes of the second window"""


# -------------------- MAIN FUNCTION --------------------


def main():
    global dots, dead_dots, axes

    dots = Dot.initalize_multiple_dots()
    random_infected = randint(0, len(dots) - 1)
    dots[random_infected].is_infected = True
    dots[random_infected].infected_at = time

    figureDots = plt.figure(facecolor="white")
    axes = plt.axes(xlim=(0, HEIGHT_WIDTH), ylim=(0, HEIGHT_WIDTH))

    global healthy_dots
    healthy_dots = axes.plot(
        [dot.x for dot in dots if not dot.is_infected],
        [dot.y for dot in dots if not dot.is_infected],
        f"g{shape}",
    )[0]

    global infected_dots
    infected_dots = axes.plot(
        [dot.x for dot in dots if dot.is_infected],
        [dot.y for dot in dots if dot.is_infected],
        f"r{shape}",
    )[0]

    global cured_dots
    cured_dots = axes.plot(
        [dot.x for dot in dots if dot.has_been_infected],
        [dot.y for dot in dots if dot.has_been_infected],
        f"b{shape}",
    )[0]
    
    global dead_dots
    dead_dots = []

    # We need to keep this in an unsed variable, otherwise the function won't work
    a = anim.FuncAnimation(figureDots, lambda z: Dot.move_all(dots), frames=60, interval=5)
    plt.axis('off')
    plt.show()


# -------------------- MAIN CALL --------------------


if __name__ == "__main__":
    main()

I'm trying to model a population with a virus and I would like to add another window where we can see in real time a graph of each type of the population (infected, healthy, dead, cured).

CodePudding user response:

Correct me if I misunderstood your question but I think you're looking for subfigures: https://matplotlib.org/devdocs/gallery/subplots_axes_and_figures/subfigures.html

You can create subfigures like so:

import matplotlib.pyplot as plt

fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)

There are multiple ways of adding subfigures, this is how I usually do it, the linked example above has another method.

The subfigures are added in the format [row column subfig_number] where subfig_number goes from the top left subfigure row-wise from left to right to the bottom right figure.

  • Related