Gráficos com SymPy e Matplotlib

sympy matplotlib feature

Neste artigo veremos como fazer gráficos com SymPy e Matplotlib.

Usualmente quando se fala de SymPy focamos na parte relacionada à resolução de problemas de cálculos matemáticos. Mas muitos esquecem que a biblioteca possui alguns recursos básicos para construção de gráficos, que veremos neste artigo.

Criando uma equação

Para ilustrar, usarei a lei dos gases ideais neste artigo. O famoso PV=nRT visto no ensino médio. Vamos verificar como fazer gráficos que mostrem a dependência do volume de um gás ideal com a pressão e com a temperatura.

Comecemos importando as bibliotecas que usaremos durante o artigo. O papel de cada uma será explicado no decorrer:

import sympy
from sympy.plotting import plot, plot3d
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation

# configuração para outputs melhores no artigo, pode ser ignorado
sympy.init_printing(use_latex='png', scale=1.0, order='grlex',
                    forecolor='Black', backcolor='White',)

Apenas para referência, a versão do SymPy nesse artigo é a 1.9, mais recente no momento que escrevo.

sympy.__version__
'1.9'

Caso não se lembre, a constante dos gases R possui vários valores possíveis a depender do sistema de unidades a ser utilizado. É muito provável que você tenha utilizado o valor “0,082” diversas vezes em sua vida por ser o valor associado às unidades atmosfera (para pressão) e litros (para volume), que são mais usuais no nosso país para essas grandezas. A temperatura sendo expressa em Kelvin. Na realidade, esse é um valor aproximado para facilitar as contas feitas manualmente ou em calculadoras simples. Aqui, não precisamos aproximar, então vamos colocar todos os significativos:

gas_constant = 0.082057366080960  # atm L / (mol K)

Na realidade, não devemos aproximar. Nunca faça aproximações em etapas intermediárias, isso só levará a erros propagados em suas contas. Mas isso é assunto para outro artigo. Apenas aproveite que está com um computador que lida muito bem com números e não faça aproximações. Se precisar de um número “comportado” para resposta, faça arredondamento apenas ao final.

Voltando ao foco, vamos agora criar símbolos para cada grandeza e criar nossa equação. Caso tenha dúvidas sobre esta etapa, veja o artigo sobre equações com SymPy:

P, V, T, R, n = sympy.symbols('P V T R n', posivite=True)

ideal_gas_law = sympy.Eq(P*V, n*R*T)
ideal_gas_law

Deixando claro o papel de cada símbolo:

  • P: pressão em atmosferas (atm)
  • V: volume em litros
  • n: quantidade de matéria em mol
  • R: constante dos gases em atm.l/(mol.K)
  • T: temperatura em Kelvin

Vamos resolver tal equação para o volume V:

sympy.solve(ideal_gas_law, V)

Veja que o resultado sai como uma lista com um único elemento, que é a expressão resultante de se isolar o V da equação no lado esquerdo da igualdade. Vamos associar tal expressão à uma variável:

volume = sympy.solve(ideal_gas_law, V)[0]  # posição 0 (primeira) da lista
volume

Há três variáveis na expressão. Dito de outra forma, V=f(P, n,T), volume é função da pressão, da quantidade de matéria e da temperatura. Vamos começar simples, obtendo uma expressão para temperatura e quantidade de matérias constantes. Para isso, vamos substituir 1 mol e 25 °C (298,15 K) na expressão:

volume_molar_25_celsius = volume.subs({R: gas_constant, n: 1, T: 298.15})
volume_molar_25_celsius

Agora estamos em condição de fazer um gráfico mostrando como o volume V se altera com variações da pressão P mantidos constantes n e T nos valores de 1 mol e 298,15 K, respectivamente.

Gráficos com SymPy

No início do artigo, vimos que importamos o método plot do SymPy. Vamos passar a expressão obtida para o volume como argumento e indicar qual a variável, no caso P:

plot(volume_molar_25_celsius, P)
<sympy.plotting.plot.Plot at 0x7f46a2f19730>

Há algumas observações interessantes sobre o gráfico gerado:

  • Quem já está mais acostumado com Python deve ter reconhecido a “cara” do Matplotlib no gráfico. E, realmente, o SymPy utiliza o Matplotlib como backend para gráficos
  • Observe como os eixos foram nomeados. A abscissa (o “eixo x”) foi nomeada como P, a variável indicada ao chamar o método plot. A ordenada (“eixo y”) foi nomeada como f(P), ou seja, indicando que esse eixo é uma função de P. Como é uma biblioteca matemática, o SymPy assume que y = f(x) e deixa explícito isso no gráfico. É possível mudar esses rótulos, mas não deixa de ser importante notar a representação padrão.
  • Mais uma característica de biblioteca matemática, o gráfico é centrado na origem (0, 0). Isso não é padrão do Matplotlib, é uma modificação feita pelo SymPy.
  • Mas, talvez, o que chame mais a atenção seja o fato de haver uma parte do gráfico em valores negativos. Por óbvio, fisicamente isto não faz sentido já que se trata de pressão e volume. Inclusive, ao criar os símbolos no início colocamos positive = True. Então, por que apareceu gráfico no 3° quadrante?

Na documentação do método plot vemos que, por padrão, a faixa de valores de domínio considerada é (-10, 10), como é possível ver no gráfico. Logo, independentemente do que pedimos para o SymPy assumir ao criar os símbolos, o gráfico será gerado para a faixa solicitada pelo Matplotlib. Mas essa é uma explicação prática.

Na realidade, temos que ter em mente que todas essas suposições que solicitamos que o SymPy faça como, por exemplo, que determinado símbolo tenha apenas valores positivos, só são válidas dentro do contexto de operações do SymPy. O SymPy é uma camada de abstração simbólica sobre o Python puro. Quando ele interage com outras bibliotecas, como no caso do Matplotlib, não há como garantir comportamentos coerentes com as suposições. As suposições são válidas dentro de operações SymPy e são importantes como vimos no artigo sobre logaritmos. Porém, cuidado com suas expectativas ao interagir com outras bibliotecas.

Tendo feito essas ressalvas, vamos restringir o domínio para uma faixa que faça mais sentido. Para isso, basta passar nossa variável e os valores mínimo e máximo em uma tupla:

plot(volume_molar_25_celsius, (P, 0.1, 4))
<sympy.plotting.plot.Plot at 0x7f46a2ee82b0>

Já temos o perfil característico de isoterma de um gás ideal. Se quisermos ainda restringir mais o gráfico, para uma faixa de valores de volume menores, basta passar os valores mínimo e máximo desejados para o eixo y para o argumento ylim na forma de uma tupla:

plot(volume_molar_25_celsius, (P, 0.1, 4), ylim=(0, 80))
<sympy.plotting.plot.Plot at 0x7f46a265d370>

E se quisermos outra curva no gráfico? Por exemplo, para 100 °C (373,15 K)? Simples, basta criar a expressão e passá-la para o método plot:

volume_molar_100_celsius = volume.subs({R: gas_constant, n: 1, T: 373.15})
plot(volume_molar_25_celsius, 
     volume_molar_100_celsius, 
     (P, 0.1, 4), 
     ylim=(0, 80))
<sympy.plotting.plot.Plot at 0x7f46a25dd580>

Vemos que foi criada uma segunda curva, referente à nova temperatura. Poderíamos continuar adicionando curvas fazendo o procedimento acima e, claro, melhorar a apresentação do gráfico identificando as curvas. Porém, logo ficará evidente que interagir com o Matplotlib por meio do SymPy é bem burocrático e limitado. Por exemplo, não faz muito sentido criar uma expressão para cada curva se o intuito for, por exemplo, apresentar curvas para 5 temperaturas no gráfico. E isto não é uma crítica ao SymPy, simplesmente não é o foco da biblioteca lidar com gráficos, já fizeram até muito em colocar uma forma simplificada de gerar gráficos. A questão é que personalizações mais avançadas ficam muito mais simples se extrairmos os dados dos objetos SymPy e passarmos diretamente para o Matplotlib.

Gráficos com Matplotlib

Vamos desbloquear o poder ilimitado de personalização do Matplolib.

matplotlib poder ilimitado

Valeu, Palpatine, vamos pro lado Matplotlib da força… Vejamos como isso funciona.

O primeiro passo é solicitar ao método plot que não apresente o gráfico em si:

plot(volume_molar_25_celsius, (P, 0.1, 4), ylim=(0, 80), show=False)
<sympy.plotting.plot.Plot at 0x7f46a254fa00>

Veja que é gerado apenas um objeto Plot em um endereço de memória. Logo, podemos associar tal objeto à uma variável. Criemos, então, uma variável com o super criativo nome de p:

p = plot(volume_molar_25_celsius, (P, 0.1, 4), ylim=(0, 80), show=False)
p
<sympy.plotting.plot.Plot at 0x7f46a26c3280>

Podemos ordenar que o gráfico seja exibido a partir dessa variável:

p.show()

Agora, para compreender os próximos passos, vamos entender de forma bem simplificada a composição de um gráfico Matplotlib. Quando você olha para um gráfico como o acima pense nele como:

  • um retângulo em branco, que é a base da figura
  • um sistema de eixos acima deste retângulo
  • o sistema de eixos possui o elemento linha, os rótulos de cada eixo, os valores apresentados no eixo e todo o resto que vemos
  • caso desejado, novos sistemas de eixo podem ser adicionados à mesma figura base
  • e, caso desejado, novas linhas (ou pontos) podem ser adicionados a um dado eixo

É basicamente assim que são criados gráficos com o Matplotib. Mais detalhes podem ser vistos na documentação da API do Matplotlib e com uma linguagem mais rigorosa. O intuito aqui é entender que podemos decompor um gráfico em elementos mais fundamentais (objetos em programação).

Assim, o objeto associado à nossa variável p é, na realidade, um container! Ele possui todos estes requisitos para criar um gráfico Matplotlib. Vamos ver todos os atributos e métodos públicos associados a esse objeto:

[x for x in dir(p) if not x.startswith('_')]
['annotations',
 'append',
 'aspect_ratio',
 'autoscale',
 'axis',
 'axis_center',
 'backend',
 'extend',
 'fill',
 'legend',
 'margin',
 'markers',
 'rectangles',
 'save',
 'show',
 'size',
 'title',
 'xlabel',
 'xlim',
 'xscale',
 'ylabel',
 'ylim',
 'yscale',
 'zlabel']

Ora, está tudo aí. As informações sobre os eixos, os rótulos, (quase) tudo que falamos anteriormente. Por exemplo, vamos conferir o rótulo do eixo y e o limite que solicitamos (de 0 a 80):

p.ylabel, p.ylim
('f(P)', (0.0, 80.0))

Ótimo! Agora, há dois métodos “suspeitos” nessa lista: append e extend. São dois métodos característicos de listas, associados à capacidade de aumentar uma lista por adição de mais elementos. Então… o objeto p foi criado com uma estrutura de lista. E qual será o conteúdo dessa lista? Vejamos a primeira posição da lista:

p[0]
<sympy.plotting.plot.LineOver1DRangeSeries at 0x7f46a2620a00>

Bingo! Um objeto LineOver1DRangeSeries, ou seja, com todas as letras, uma linha sobre um intervalo de valores unidimensionais. Traduzindo, nossa curva. E, como só há uma curva, não faz sentido ter um elemento na posição seguinte, correto?

p[1]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
/tmp/ipykernel_8614/3900177224.py in <module>
----> 1 p[1]

/opt/anaconda/lib/python3.8/site-packages/sympy/plotting/plot.py in __getitem__(self, index)
    249 
    250     def __getitem__(self, index):
--> 251         return self._series[index]
    252 
    253     def __setitem__(self, index, *args):

IndexError: list index out of range

Correto! Logo, já sabemos que novas curvas são novos elementos nessa lista que é o objeto p. E quais os atributos e métodos públicos disponíveis para cada elemento dessa lista?

[x for x in dir(p[0]) if not x.startswith('_')]
['adaptive',
 'depth',
 'end',
 'expr',
 'get_color_array',
 'get_data',
 'get_points',
 'get_segments',
 'is_2Dline',
 'is_3D',
 'is_3Dline',
 'is_3Dsurface',
 'is_contour',
 'is_implicit',
 'is_line',
 'is_parametric',
 'label',
 'line_color',
 'nb_of_points',
 'only_integers',
 'start',
 'steps',
 'var',
 'xscale']

Vemos, pelos nomes, que são informações sobre a curva em si: sua cor, quantidade de pontos e os pontos em si. E são os pontos que queremos, para poder construir nossos próprios gráficos diretamente com o Matplotlib. Podemos obter os pontos com o método get_points (sugestivo, não?):

# a quantidade de valores é bem longa, então usei um pouquinho de NumPy aqui
# apenas para mostrar os três primeiros e os três últimos valores do eixo x
# e do eixo y de uma forma mais amigável. Não é necessário para obter os
# pontos em si, é apenas um artifício de apresentação para o artigo

with np.printoptions(threshold=10, precision=3, suppress=True):
    display(np.array(p[0].get_points()))
array([[  0.1  ,   0.167,   0.242, ...,   3.89 ,   3.943,   4.   ],
       [244.654, 146.637, 101.282, ...,   6.29 ,   6.204,   6.116]])

Como podemos ver, há os valores do “eixo x” de 0.1 até 4 na primeira lista, conforme solicitado ao chamar o método plot e os valores de y associados na segunda lista. Vamos ver a quantidade de elementos em cada lista:

len(p[0].get_points()[0]), len(p[0].get_points()[1])

A quantidade de pontos é estimada pelo próprio SymPy. É possível personalizar, mas sinceramente nunca achei necessário, as quantidades calculadas pelo SymPy sempre me forneceram gráficos bem apresentados.

Vamos entender a estrutura do objeto gerado pelo get_points. Já vimos que há duas listas dentro do objeto, mas vejamos o tipo do objeto:

type(p[0].get_points())
tuple

Assim, vemos que é uma tupla e, dentro da tupla, há uma lista para os valores de x e outra para os de y. Já sabemos tudo que precisamos. Vamos associar uma variável a esse objeto, criar uma figura do Matplotlib e fazer nosso gráfico:

data = p[0].get_points()

fig, ax = plt.subplots(figsize=(8, 6), facecolor=(1, 1, 1))

ax.plot(*data)  # unpack da tupla
[<matplotlib.lines.Line2D at 0x7f46a2387f70>]

OK, chegamos mais ou menos ao que já tínhamos… e daí?

Daí que agora podemos automatizar todo o processo de criação. Vamos gerar um gráfico com 6 temperaturas. Se fôssemos fazer via SymPy, seria um procedimento meio tedioso, criando cada expressão e passando uma a uma para o método plot do SymPy. Agora podemos fazer isso de forma mais ágil, pois sabemos extrair os dados do SymPy.

Vamos começar criando nossos valores de temperatura. Ninguém merece fazer conta de cabeça em Kelvin, então vamos pensar em valores redondos em Celsius e deixar o Python fazer a conversão para a gente:

temperatures_celsius = (-100, 0, 25, 100, 500, 1000)
temperatures_kelvin = tuple(t + 273.15 for t in temperatures_celsius)

Agora, pense. Queremos seis gráficos, precisamos dos dados de cada um. Vamos criar uma lista vazia para armazenas esses dados. E esses dados precisam ser extraídos de expressões do SymPy com o get_points. Podemos criar cada expressão e extrair, mas não precisamos efetivamente criar uma expressão para cada temperatura, podemos reaproveitar a mesma variável, não há interesse em objetos para cada expressão. Basicamente, descrevemos um loop for:

plots = []

for temperature in temperatures_kelvin:
    expr = volume.subs({R: gas_constant, n: 1, T: temperature})
    p = plot(expr, (P, 0.1, 4), show=False)
    data = p[0].get_points()
    plots.append(data)

Veja que dentro do loop fizemos o mesmo procedimento descrito anteriormente no artigo. Mas fomos extraíndo os dados em cada passagem e anexando (append) em nossa lista. Assim, nossa lista possui 6 conjuntos de dados, um conjunto de pontos (x, y), ou melhor (P, V), para cada temperatura:

len(plots)

Agora é usar o poder do Matplotib. Seguindo a composição que falamos anteriormente, vamos criar uma figura e associar sistemas de eixos à essa figura, cada sistema com uma das curvas. E, claro, identificar a figura e cada eixo, além de colocar legenda. Serviço completo:

fig, ax = plt.subplots(figsize=(12, 8), facecolor=(1, 1, 1))

for i, p in enumerate(plots):
    ax.plot(*p, label=f'T = {temperatures_celsius[i]} °C', linewidth=3)
    ax.set_ylim(0, 80)
    ax.legend(fontsize=14)
    ax.set_ylabel('Volume / liter', fontsize=16)
    ax.set_xlabel('Pressure / atm', fontsize=16)

ax.set_title("Molar volume - ideal gas", fontsize=18)

plt.show()

Que satisfação, Aspira! O procedimento pode ser agora extraído para um script, por exemplo, e reproduzido para qualquer outro caso, não necessariamente com a lei de gases ideais mas com qualquer outra equação com apenas uma variável.

Mas 2D é para os fracos, queremos em 3D!

gráfico 3D sympy

Gráficos em 3D

Vamos nos lembrar de objetos criados lá no início:

ideal_gas_law
volume

No início, fixamos as temperaturas em alguns valores e geramos gráficos em 2D, que são isotermas. Mas, agora queremos que a temperatura seja um dos eixos do gráfico. Assim, a única variável que fixaremos é a quantidade de matéria em mol, no valor de 1 mol:

volume_molar = volume.subs({R: gas_constant, n: 1})
volume_molar

No início, importamos do SymPy o método plot3d. A forma de usá-lo é muito similar ao visto para o plot, sendo que agora passaremos intervalos não apenas para a pressão mas, também, para a temperatura:

plot3d(volume_molar, (P, 0.1, 4), (T, 0, 1000))
<sympy.plotting.plot.Plot at 0x7f46a23bf9a0>

Assim, geramos uma superfície PVT com a pressão no intervalo entre 0,1 e 4 atm e a temperatura no intervalo de 0 a 1000 K. Todos os gráficos em duas dimensões que você já viu ao estudar gases nada mais são que cortes em planos de superfícies como essa, mantendo algo constante: a temperatura (gráfico de isotermas), a pressão (gráfico de isóbaras) ou o volume (gráfico de isométricas).

Veja que o SymPy já busca dar alguns toques no gráfico como, por exemplo, usando um gradiente de cores na superfície, indicando com cores mais quentes maiores valores do eixo z, no nosso caso o eixo de volume. Mas, queremos ter ainda mais poder de personalização, então vamos extrair os dados. O procedimento é análogo ao visto anteriormente:

p = plot3d(volume_molar, (P, 0.1, 4), (T, 0, 1000), show=False)
p
<sympy.plotting.plot.Plot at 0x7f46a06d8d30>
p[0]
<sympy.plotting.plot.SurfaceOver2DRangeSeries at 0x7f46a06d8f40>

Veja agora que o objeto é distinto, é SurfaceOver2DRangeSeries, literalmente uma superfície sobre uma faixa de valores em duas dimensões (P e T). Faz total sentido no nosso contexto. Vejamos os atributos e métodos públicos associados a esse objeto:

[x for x in dir(p[0]) if not x.startswith('_')]
['end_x',
 'end_y',
 'expr',
 'get_color_array',
 'get_meshes',
 'is_2Dline',
 'is_3D',
 'is_3Dline',
 'is_3Dsurface',
 'is_contour',
 'is_implicit',
 'is_line',
 'is_parametric',
 'nb_of_points_x',
 'nb_of_points_y',
 'start_x',
 'start_y',
 'surface_color',
 'var_x',
 'var_y']

Veja que não há um get_points e faz sentido não haver. Quando lidamos com gráficos de superfícies em 3 dimensões o que temos são pares de coordenadas (x, y) associados a valores em z. Esses pares constroem o que usualmente é chamado de mesh grid (mesh é malha em inglês). Tanto que há um método no NumPy para criar meshgrids que você verá em diversos exemplos da própria documentação do Matplotlib na geração de superfícies em 3D.

Assim, reconhecemos que o que queremos é fornecido pelo get_meshes:

# a quantidade de valores é bem longa, então usei um pouquinho de NumPy aqui
# apenas para mostrar os três primeiros e os três últimos valores das matrizes
# de uma forma mais amigável. Não é necessário para obter os
# valores em si, é apenas um artifício de apresentação para o artigo

with np.printoptions(threshold=10, precision=3, suppress=True, linewidth=100):
    display(np.array(p[0].get_meshes()))
array([[[   0.1  ,    0.18 ,    0.259, ...,    3.841,    3.92 ,    4.   ],
        [   0.1  ,    0.18 ,    0.259, ...,    3.841,    3.92 ,    4.   ],
        [   0.1  ,    0.18 ,    0.259, ...,    3.841,    3.92 ,    4.   ],
        ...,
        [   0.1  ,    0.18 ,    0.259, ...,    3.841,    3.92 ,    4.   ],
        [   0.1  ,    0.18 ,    0.259, ...,    3.841,    3.92 ,    4.   ],
        [   0.1  ,    0.18 ,    0.259, ...,    3.841,    3.92 ,    4.   ]],

       [[   0.   ,    0.   ,    0.   , ...,    0.   ,    0.   ,    0.   ],
        [  20.408,   20.408,   20.408, ...,   20.408,   20.408,   20.408],
        [  40.816,   40.816,   40.816, ...,   40.816,   40.816,   40.816],
        ...,
        [ 959.184,  959.184,  959.184, ...,  959.184,  959.184,  959.184],
        [ 979.592,  979.592,  979.592, ...,  979.592,  979.592,  979.592],
        [1000.   , 1000.   , 1000.   , ..., 1000.   , 1000.   , 1000.   ]],

       [[   0.   ,    0.   ,    0.   , ...,    0.   ,    0.   ,    0.   ],
        [  16.746,    9.325,    6.461, ...,    0.436,    0.427,    0.419],
        [  33.493,   18.649,   12.922, ...,    0.872,    0.854,    0.837],
        ...,
        [ 787.081,  438.261,  303.677, ...,   20.493,   20.077,   19.677],
        [ 803.827,  447.586,  310.138, ...,   20.929,   20.504,   20.096],
        [ 820.574,  456.91 ,  316.599, ...,   21.365,   20.931,   20.514]]])

Vamos associar uma variável:

data = p[0].get_meshes()

Agora temos controle total sobre o que fazer. Por exemplo, não entendeu muito bem a parte de mesh? Vamos fazer um gráfico só de pontos com os dados:

fig = plt.figure(figsize=(12, 8), facecolor=(1, 1, 1))
ax = fig.add_subplot(111, projection='3d')

ax.scatter(*data)
ax.set_xlabel('Pressure / atm')
ax.set_ylabel('Temperature / K')
ax.set_zlabel('Volume / l')

plt.show()

Veja como são criadas “linhas”, valores sequenciais no plano pressão x temperatura, tanto linhas paralelas ao eixo de pressão quanto paralelas ao eixo de temperatura. Repare, na representação apresentada mais acima dos valores que são fornecidos pelo get_meshes, como há repetição de valores. É essa a tela (mesh) de valores pareados (P, T). E a cada par temos associado um valor de volume V apresentado no eixo vertical. Espero que tenha ajudado a entender melhor.

Particularmente, a representação que acho mais útil é a de “arame” (wireframe):

fig = plt.figure(figsize=(12, 8), facecolor=(1, 1, 1))
ax = fig.add_subplot(111, projection='3d')

ax.plot_wireframe(*data, rstride=5, cstride=5)
ax.set_xlabel('Pressure / atm')
ax.set_ylabel('Temperature / K')
ax.set_zlabel('Volume / l')

plt.show()

Consegue reconhecer as isotermas e as isóbaras no gráfico?

Por fim, podemos também criar superfícies:

fig = plt.figure(figsize=(12, 8), facecolor=(1, 1, 1))
ax = fig.add_subplot(111, projection='3d')

ax.plot_surface(*data)
ax.set_xlabel('Pressure / atm')
ax.set_ylabel('Temperature / K')
ax.set_zlabel('Volume / l')

plt.show()

E colorir com qualquer colormap disponível no Matplotlib:

fig = plt.figure(figsize=(12, 8), facecolor=(1, 1, 1))
ax = fig.add_subplot(111, projection='3d')

ax.plot_surface(*data, cmap='rainbow')
ax.set_xlabel('Pressure / atm')
ax.set_ylabel('Temperature / K')
ax.set_zlabel('Volume / l')

plt.show()

E, já que estamos aqui, vamos fazer esse gráfico rodar. Por que? Porque sim, o gráfico é meu e faço o que quiser 😉

%matplotlib notebook
fig = plt.figure(figsize=(6, 4), facecolor=(1, 1, 1))
ax = fig.add_subplot(111, projection='3d')

ax.plot_surface(*data, cmap='rainbow')
ax.set_xlabel('Pressure / atm')
ax.set_ylabel('Temperature / K')
ax.set_zlabel('Volume / l')

def animate(angle):    
    ax = plt.gca()  # gca = get current axis
    ax.view_init(30, angle)    
    
anim = animation.FuncAnimation(fig,
                               animate,
                               frames=361,  # 360° rotation  
                               interval=1,
                               repeat=False)

plt.show()
anim sympy matplolib 3D

Conclusão

Duas das bibliotecas que mais gosto em um mesmo artigo. Por mais que este artigo seja parte da série sobre SymPy, muito foi discutido sobre a forma de construção de gráficos do Matplotlib. Esse conhecimento é fundamental para conseguir extrair ao máximo tudo que é possível fazer em termos de gráficos. Vimos também como o SymPy se comporta quando interage com outras bibliotecas, como nem sempre instruções internas do SymPy são repassadas a outras bibliotecas.

Caso queira saber quando novos artigos são disponibilizados, siga o Ciência Programada nas redes sociais. A lista completa de artigos sobre SymPy pode ser vista na tag SymPy e, caso queira ver a série na ordem, veja a lista abaixo, que será atualizada a cada novo artigo:

Até a próxima.

Compartilhe:

2 comentários em “Gráficos com SymPy e Matplotlib”

  1. professor, parabéns pelo trabalho. tenho uma dúvida muito importante. No momento estou trabalhando num grafico em escalas log, log e preciso plotar retas para comparar com os dados plotados. entretanto, a função axline nao plota retas infinitas com inclinaçoes em graficos com escalas logaritmicas. O senhor sabe alguma saída para solucionar meu problema?
    mais uma vez, parabéns. Muito obrigado.

    1. Bom dia, Rodrigo. Obrigado pelo elogio.

      Olha, a função axline pode ser usada em escalas log-log sim. Não sei exatamente qual o problema encontrado por você, se ocorreu algum erro ou o gráfico gerado não corresponde ao esperado, mas verifique que está passando coordenadas de dois pontos corretamente. Tendo algum link de repositório com os dados, posso dar uma olhada e ver se consigo entender a situação.

      Abraço

      Francisco

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima