Как стать автором
Обновить

Построение графиков функций на Python. Pygame

Привет, Хабр! Я хотел бы рассказать, как написать программу для построения графиков функций. Программу будем писать на Python с использованием библиотеки PyGame.

P.S. Программа простая и предназначена больше для учебных целей, чем для повседневного использования(хотя и с этим алгоритм может справится).

Основной алгоритм

Основной алгоритм заключается в вычислении функции на интервале: [-10,10]. Так как целых значений немного, то будем увеличивать параметр с малым шагом, к примеру 0.0001.

Функция имеет вид y = f(x). Формат ввода, соответственно, string(так как пользователь вводить функцию с использованием символов).

Получается следующий алгоритм:

Пишем код

Для начала следует импортировать библиотеки. Это библиотека PyGame, с её помощью будем рисовать. И библиотека math, она добавляет больше математических функций(sin, cos, sqrt и т.д.).

from math import *
import pygame 

Теперь напишем несколько системных строк, которые создадут нам окно, в котором будет находится система координат.

pygame.init() # запустить pygame
screen = pygame.display.set_mode((500,500)) # создать окно 500x500
pygame.display.set_caption("func_graph") # название окна
all_sprites=pygame.sprite.Group() # группа спрайтов  
clock = pygame.time.Clock() 
running = True
fps=60

Создадим класс, который будет рисовать координатные оси(ординат и абсцисс).

class Line(pygame.sprite.Sprite):
    def __init__(self,pos,x,y):
        pygame.sprite.Sprite.__init__(self)
        if pos=="x": # ось абсцисс
            self.image=pygame.Surface((3,400)) # линия 3x400
            self.image.fill((0,0,0)) # закрасить черным цветом
            self.rect = self.image.get_rect()
            self.rect.centerx = x # центр по x
            self.rect.centery = y # центр по y
        elif pos=="y": # ось ординат 
            self.image=pygame.Surface((400,3)) # линия 3x400
            self.image.fill((0,0,0)) # закрасить черным цветом
            self.rect = self.image.get_rect()
            self.rect.centerx = x # центр по x
            self.rect.centery = y # центр по y

Теперь класс для точки, так как точек будет очень много, то график будет казаться сплошным.

class Dot(pygame.sprite.Sprite):
    def __init__(self,x,y):
        pygame.sprite.Sprite.__init__(self)
        self.image=pygame.surface.Surface((5,5)) # точка 5x5
        self.image.fill((0,0,0)) # закрасить черным цветом
        self.rect=self.image.get_rect()
        self.rect.centerx=x # центр по x
        self.rect.centery=y # центр по y

Теперь приступим к основному алгоритму. Функцию считает модуль eval, а с ним нужно быть очень аккуратным в плане ввода(т.е. "x" желательно брать в скобки, чтобы модуль учитывал знак минус, степень - это два знака умножить(**), и т.д.). Конечно, можно сделать так, чтобы ввод пользователя преобразовывался в нормальный вид, это аналогично части с заменой "x" на значение(строка 6), но и тут есть свои проблемы.

def Calc(func):
    i=-10 # начальное значение аргумента
    while i<=10: # пока аргумент меньше 10
        mass="" # темп-строка
        for j in func: # для каждого символа в строке func(наша функция)
            if j == "x": # если символ = x, то добавляем i в темп-строку
                mass+=str(i)
            else: # если нет, то добавить исходный символ
                mass+=j
            i+=0.0001 # увеличить аргумент на 0.0001
        try:
          res1=eval(mass) # посчитать функцию и получить результат
        except:
          res1=10000 # если функцию нельзя посчитать, то результат число вне координат(знаю, костыль)
        dot=Dot(250+i*10,250-res1*10) # dot - точка с координатой(0+x,0+y), так как это дисплей, то вектор "y" направлен вниз
        all_sprites.add(dot) # добавить точку в группу спрайтов

Теперь заключительная часть кода.

func = str(input("y = ")) # ввод данных 
calc = Calc(func) # вызвать функцию Calc от func

line = Line("y",250,250) # добавить ось ординат
all_sprites.add(line)
line1 = Line("x",250,250) # добавить ось абсцисс
all_sprites.add(line1)

while running: # основной цикл
    clock.tick(fps)
    for event in pygame.event.get():
        if event.type == pygame.QUIT: # если закрыл окно - завершить программу
            running = False
    screen.fill((255,255,255)) # залить поле белым цветом
    all_sprites.draw(screen) # нарисовать все спрайты(т.е. наши точки)
    pygame.display.flip()
pygame.quit()

Вот такой получился код:

from math import *
import pygame 

pygame.init()
screen = pygame.display.set_mode((500,500))
pygame.display.set_caption("func_graph")
all_sprites=pygame.sprite.Group()
clock = pygame.time.Clock()
running = True
fps=60

class Line(pygame.sprite.Sprite):
    def __init__(self,pos,x,y):
        pygame.sprite.Sprite.__init__(self)
        if pos=="x":
            self.image=pygame.Surface((3,400))
            self.image.fill((0,0,0))
            self.rect = self.image.get_rect()
            self.rect.centerx = x
            self.rect.centery = y
        elif pos=="y":
            self.image=pygame.Surface((400,3))
            self.image.fill((0,0,0))
            self.rect = self.image.get_rect()
            self.rect.centerx = x
            self.rect.centery = y
            
class Dot(pygame.sprite.Sprite):
    def __init__(self,x,y):
        pygame.sprite.Sprite.__init__(self)
        self.image=pygame.surface.Surface((5,5))
        self.image.fill((0,0,0))
        self.rect=self.image.get_rect()
        self.rect.centerx=x
        self.rect.centery=y
            
def Calc(func):
    i=-10
    while i<=10:
        mass=""
        for j in func:
            if j == "x":
                mass+=str(i)
            else:
                mass+=j
            i+=0.0001
        try:
          res1=eval(mass)
        except:
          res1=10000
        dot=Dot(250+i*10,250-res1*10)
        all_sprites.add(dot)

func = str(input("y = "))
calc = Calc(func)

line = Line("y",250,250)
all_sprites.add(line)
line1 = Line("x",250,250)
all_sprites.add(line1)

while running:
    clock.tick(fps)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    screen.fill((255,255,255))
    all_sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

Итоги

В заключение можно сказать, что получилась простая программа, которая справляется со своими функциями, Ба-Дум-Тсс. Конечно, здесь есть свои проблемы(в основном - модуль eval), но главная задача статьи - понять, как компьютер строит графики.

P.S. Код ещё можно доработать. К примеру, я доработал программу до discord бота. Если будет интересно, то я могу написать об этом. Так как я заметил, что на Хабре есть только ознакомительные статьи про ботов.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.