The challenge seemed pretty easy, to assemble an easy demo with simple example :
- One example on how to use text entry in matplotlib that doubles with a 2D plotter,
- and the one from matplotlib on how to draw a 3D surface with a colorbar
- and the one for projecting the contour of the function on the axis
Without the colorbar I would have just been slightly annoyed by the slowness of the reaction of matplotlib as a GUI, but the colorbar posed a new challenge because it would either stack for each drawing or make plt.clf/ax.cla erase too much (see this great resource on when to use cla/clf in matplotlib).
So ... I tried python-tk with matplotlib knowing all too well that you can embed natively matplotlib in tkinter interface.
And since it was working I kept it.
Here is a screenshot of the interface : WARNING this code should not be let in inventive hands (such as bored teenagers) because there is an evil eval; it requires to be in care of consenting adults.
Some highlights of the code :
- bidir python-tk requires setting the Popen PIPEs to non blocking and using select.select on the output
- matplotlib is unusable in non blocking mode : once matplotlib has the focus you need to destroy it to plot another function
- from np import * is evil, but it let you have access to all array oriented math function (sin, cos, exp, ...)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from time import time, sleep | |
from subprocess import Popen, PIPE | |
import os | |
import matplotlib.pyplot as plt | |
import numpy as np | |
from numpy import * | |
import select | |
from matplotlib import cm | |
# let's talk to tk/tcl directly | |
p = Popen(['wish'], stdin=PIPE, stdout=PIPE, stderr=PIPE) | |
os.set_blocking(p.stdout.fileno(), False) | |
os.set_blocking(p.stdin.fileno(), True) | |
os.set_blocking(p.stderr.fileno(), False) | |
def puts(s): | |
for l in s.split("\n"): | |
select.select([], [p.stdin],[]) | |
p.stdin.write((l + "\n").encode()) | |
p.stdin.flush() | |
if back := p.stderr.read(): | |
print("TCL>" + back.decode()) | |
def gets(): | |
ret=p.stdout.read() | |
if ret: | |
exec(ret, globals()) | |
xmin = -5 | |
xmax = 5 | |
xstep = .20 | |
ymin = -5 | |
ymax = 5 | |
ystep = .20 | |
zmin = -1 | |
zmax = 1 | |
puts(f""" | |
set code [ catch {{ | |
source /usr/share/tcltk/ttkthemes/themes/plastik/plastik.tcl | |
ttk::style theme use plastik | |
}} pass ] | |
if {{$code}} {{ | |
puts "print('theme not found, fallback to ugly tcl/tk look')" | |
puts "print('on debian : apt install tcl-ttkthemes to have ttkthemes installed')" | |
}} | |
set text "cos(x) * sin(y) / ( x**2 + y**2 + 1)" | |
proc submit {{}} {{ | |
global text xmax xmin xstep ymax ymin ystep zmax zmin | |
puts "compute('$text');xmax=$xmax;xmin=$xmin;xstep=$xstep;ymin=$ymin;ymax=$ymax;ystep=$ystep;zmin=$zmin;zmax=$zmax" | |
}} | |
pack [ ttk::frame .a ] -anchor s -fill both -expand 1 | |
pack [ ttk::labelframe .t -text plotter ] -padx 5 -pady 5 -fill both -expand 1 -in .a | |
pack [ ttk::frame .f ] -in .t -anchor s -fill both -expand 1 | |
pack [ ttk::label .l -text "Function to plot" ] -in .f -side left | |
pack [ ttk::entry .e -textvariable text -width 32 ] -in .f -side left | |
#bind .e <Enter> submit | |
set xmin {xmin} | |
set xmax {xmax} | |
set xstep {xstep} | |
pack [ ttk::frame .g ] -in .t -anchor s -fill both -expand 1 | |
pack [ ttk::label .lxmin -text "xmin " ] -in .g -side left | |
pack [ ttk::entry .xmin -textvariable xmin -width 3 ] -in .g -side left | |
pack [ ttk::label .lxmax -text " xmax " ] -in .g -side left | |
pack [ ttk::entry .xmax -textvariable xmax -width 3 ] -in .g -side left | |
pack [ ttk::label .lxstep -text " by " ] -in .g -side left | |
pack [ ttk::entry .xstep -textvariable xstep -width 3 ] -in .g -side left | |
set ymin {ymin} | |
set ymax {ymax} | |
set ystep {ystep} | |
pack [ ttk::frame .h ] -in .t -anchor s -fill both -expand 1 | |
pack [ ttk::label .lymin -text "ymin " ] -in .h -side left | |
pack [ ttk::entry .ymin -textvariable ymin -width 3 ] -in .h -side left | |
pack [ ttk::label .lymax -text " xmax " ] -in .h -side left | |
pack [ ttk::entry .ymax -textvariable ymax -width 3 ] -in .h -side left | |
pack [ ttk::label .lystep -text " by " ] -in .h -side left | |
pack [ ttk::entry .ystep -textvariable ystep -width 3 ] -in .h -side left | |
set zmin {zmin} | |
set zmax {zmax} | |
pack [ ttk::frame .i ] -in .t -anchor s -fill both -expand true | |
pack [ ttk::label .lzmin -text "zmin " ] -in .i -side left | |
pack [ ttk::entry .zmin -textvariable zmin -width 3 ] -in .i -side left | |
pack [ ttk::label .lzmax -text " xmax " ] -in .i -side left | |
pack [ ttk::entry .zmax -textvariable zmax -width 3 ] -in .i -side left | |
pack [ ttk::frame .j ] -anchor s -fill both -expand true | |
pack [ ttk::label .lwar -text "Close the matplotlib windows to change the function or parameters" ] -in .j -anchor s | |
set status "" | |
pack [ ttk::label .status -textvariable status ] -in .j -anchor s | |
pack [ ttk::button .b -text Submit -command submit ] -anchor s -in .j | |
""") | |
def compute(text): | |
global plt | |
fig, ax = plt.subplots() | |
ax = fig.add_subplot(projection="3d") | |
xa= np.arange(xmin,xmax,xstep) | |
ya= np.arange(ymin,ymax,ystep) | |
x, y = np.meshgrid(xa,ya) | |
ax.set_zlim(zmin,zmax) | |
try: | |
z= eval(text) | |
s = ax.plot_surface(x,y,z,cmap=cm.coolwarm, antialiased=True) | |
ax.contourf(x, y, z, zdir='z', offset=zmin, cmap='coolwarm') | |
ax.contourf(x, y, z, zdir='x', offset=xmin-xstep, cmap='coolwarm') | |
ax.contourf(x, y, z, zdir='y', offset=ymin-ystep, cmap='coolwarm') | |
f = fig.colorbar(s, shrink=0.5, aspect=10) | |
plt.ioff() | |
plt.show() | |
except Exception as e: | |
puts(f"""tk_messageBox -icon error -message "Python Error : {e}" """) | |
#compute("cos(x)*cos(y)") | |
while True: | |
gets() | |
sleep(1) |
No comments:
Post a Comment