Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Do a dynamic graph with ui and pyplot
-
Hello!
I am new to pythonista but found very interesting this approach of turning an ipad into a programmable device...I would love to be able to use it for some teaching activities, but I would need to be able to plot a curve that is changed dynamically by moving sliders.
I can plot the curve in pythonsta
I can do sliders in pythonista
I can plot the dynamic curve with other python interfaces (jupyter notebook, using interact from ipywidgets)
but I wasn't able to find any indication if that would be possible with pythonista.So is it possible and if yes, is there an example somewhere?
Thanks for any help
-
@chjl07 , hey. I am way out of my depth on this question. The math too much for me. But I think I can see a solution even i cant wok it out. If you save the pic to a file, you could create a ui that has the sliders, each time the sliders are changed you rewrite and reload the image into a ui.Image control. There may be a direct way to do this, but this is a my dumb simple idea to do it. Could be a little slow, maybe not. I had problems understanding the arr param to plt.imsave. So i could not go further. I am guessing this is the worse way to approach this problem. But sometimes the worse way, can shine some light to the real answer.
import matplotlib.pyplot as plt x = np.arange(0, 5, 0.1); y = np.sin(x) plt.plot(x, y) plt.show() plt.imsave(fname='crap', arr=(0,0,0))
-
@chjl07 , I found a few entries on the forum about pyplot. So i did something extra that shows my simple approach. It's slow and I dont eve understand what its doing, plotting wise. Is not important, I am sure you do. Not sure how much speed is an issue for you, but i think you can see from the basic example below, you could add multiple sliders to control different variables.
Again, i dont understand they math or the matplotlib. But the pattern I am using here is to do a live update as the slider moves. Another approach could be set all your sliders, then have a button you click that does the drawing using the values of the sliders.
Again, sorry, i dont really know this, just wanted to try to help. I think other guys on the forum will have a smarter approach.import ui import numpy as np import matplotlib.pyplot as plt from io import BytesIO class MyClass(ui.View): def __init__(self, p1=1, p2=2, p3=3, *args, **kwargs): super().__init__(*args, **kwargs) self.p1 = p1 self.p2 = p2 self.p3 = p3 self.img = None self.make_view() def make_view(self): slider1 = ui.Slider(x=0, y=self.height-32 , width=self.bounds.width, value=self.y, action=self.slider_action) self.add_subview(slider1) self.img = ui.ImageView(frame=self.bounds.inset(30, 30)) self.add_subview(self.img) self.set_needs_display() def draw(self): # called by ui Module when set_needs_display() is called. # as per the docs you should not call this method yourself. plt.plot([self.p1, self.p2, self.p3]) b = BytesIO() plt.savefig(b) img = ui.Image.from_data(b.getvalue()) self.img.image = img def slider_action(self, sender): self.p1 *= sender.value+1 self.p2 *= sender.value+2 self.p3 *= sender.value+3 self.set_needs_display() if __name__ == '__main__': f = (0, 0, 400, 400) mc= MyClass(frame=f, name='*** WTF ***') mc.present('sheet')
-
Hi Phuket2
Thanks a lot. your example works on my ipad and I think this approach could work. I have been using the approach of designing the ui with pyui, right now I am still trying to figure out how to pass the image to the ui...x = np.linspace(0,9,100) plt.show() img=plt.plot(x,np.sin(x)) v['imageview1'].img=img
doesnt deliver any error, but doesnt do the job either...
-
Sorry, I was too fast typing my last message. I meant :
x = np.linspace(0,9,100) img=plt.plot(x,np.sin(x)) plot.show() v['imageview1'].img=img
the plot.show shows the curve in the background. But I have no curve in the user interface
-
Thanks again. So finally it worked. Here the total code. This plots the parabolic trajectory as a function of angle and initial velocity. I just need to figure out how to delete the previous image and not have them superimposed when the sliders are used.
import matplotlib.pyplot as plt import numpy as np import ui from io import BytesIO g=9.81 def plotparabole(alpha,v_0): #Le programme calcule en radians, mais un input en degres est plus intuitif alpharad=alpha*np.pi/180 #le graphe sera de 100 points, pour un temps entre 0 et 9 secondes, #mais suivant les conditions, le temps de vol réel peut être plus court t = np.linspace(0,9,100) #représentation du vecteur vitesse à t=0 vit=[0,0,v_0*np.cos(alpharad),v_0*np.sin(alpharad)] ax = plt.gca() ax.quiver(0,0,v_0*np.cos(alpharad),v_0*np.sin(alpharad),color='red',angles='xy', scale_units='xy', scale=1) plt.axis([0, 170, 0, 100]) #tracé de la trajectoire, grâce à des coordonnées parametrées en fonction du temps plt.plot(v_0*np.cos(alpharad)*t, -0.5*g*t**2+v_0*np.sin(alpharad)*t) plt.axes().set_aspect('equal') b = BytesIO() plt.savefig(b) img = ui.Image.from_data(b.getvalue()) return(img) ######################################################### def slider_action(sender): # Get the root view: v = sender.superview # Get the sliders: v0 = v['slider1'].value*40 alpha = v['slider2'].value*90 v['alpha'].text = 'alpha=%f degres' % (alpha) v['v0'].text = 'vitesse=%f m/s' % (v0) img=plotparabole(alpha,v0) v['imageview1'].image=img v = ui.load_view('Balistique') slider_action(v['imageview1']) if ui.get_screen_size()[1] >= 768: # iPad v.present('sheet') else: # iPhone v.present()
and here the pyui file:
[ { "selected" : false, "frame" : "{{0, 0}, {560, 427}}", "class" : "View", "nodes" : [ { "selected" : false, "frame" : "{{15, 380}, {242, 34}}", "class" : "Slider", "nodes" : [ ], "attributes" : { "flex" : "W", "border_width" : 1, "action" : "slider_action", "frame" : "{{180, 168}, {200, 34}}", "border_color" : "RGBA(1.000000,0.200000,0.200000,1.000000)", "class" : "Slider", "value" : 0.5, "uuid" : "0EBE87B6-3527-4D66-B9D6-F1361D7BB24F", "corner_radius" : 2, "name" : "slider1" } }, { "selected" : false, "frame" : "{{295, 387}, {232, 34}}", "class" : "Slider", "nodes" : [ ], "attributes" : { "action" : "slider_action", "flex" : "W", "frame" : "{{180, 168}, {200, 34}}", "uuid" : "6D51E0F1-9BE3-4486-8962-D365342746A5", "class" : "Slider", "value" : 0.5, "name" : "slider2" } }, { "selected" : false, "frame" : "{{295, 340}, {198, 32}}", "class" : "Label", "nodes" : [ ], "attributes" : { "font_name" : "<System>", "frame" : "{{205, 169}, {150, 32}}", "uuid" : "733AE3FF-A92D-41CD-B37D-66EF27B4DD3A", "class" : "Label", "alignment" : "left", "text" : "alpha", "font_size" : 18, "name" : "alpha" } }, { "selected" : false, "frame" : "{{15, 340}, {178, 32}}", "class" : "Label", "nodes" : [ ], "attributes" : { "font_name" : "<System>", "frame" : "{{205, 169}, {150, 32}}", "uuid" : "733AE3FF-A92D-41CD-B37D-66EF27B4DD3A", "class" : "Label", "alignment" : "left", "text" : "v0", "name" : "v0", "font_size" : 18 } }, { "selected" : true, "frame" : "{{15, 28}, {524, 304}}", "class" : "ImageView", "nodes" : [ ], "attributes" : { "alpha" : 1, "frame" : "{{230, 164}, {100, 100}}", "class" : "ImageView", "background_color" : "RGBA(0.956522,1.000000,0.869565,1.000000)", "uuid" : "60FC8613-07A2-4229-ACFE-B8DD87CC03FE", "name" : "imageview1", "image_name" : "iob:alert_256" } } ], "attributes" : { "enabled" : true, "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "flex" : "" } } ]
-
@chjl07 said:
I just need to figure out how to delete the previous image and not have them superimposed when the sliders are used.
Use this to close the previous plot.
plt.close()
--
JJW -
@chjl07 , thats great. Glad you got it working. As I said I didn't know what I was doing, but could sort of imagine how it could be solved.
Look if you are happy with what you have thats fine. But there is a way to load a UIFile into a regular Custom View Class. Can make working with your pyui so much easier. I have put the code below. If you search the forum you will see where this code came from , essentially @JonB.
But its a pretty nice way to handle views created in the ui designer.You have to do things.
- pass the filename of your pyui file to MyClass (pretty evident)
- Can not show here, but you have to set the 'Custom View Class' property in the ui Designer of of your form to the name of the class name you are using. In this example, the class I want to bring the pyui file into is called MyClass. So, eg, if you decided to call you class MYPlotClass, you would but that in the 'Custom View Class' property into your form.
Hope I have not made it sound complicated because its not.
Look again, this may not be important to you. But for me its great to be able to get a UIFile loaded into a class the same as if you had created a CustomView.
It's worth a look.
import ui def pyui_bindings(obj): # JonB def WrapInstance(obj): class Wrapper(obj.__class__): def __new__(cls): return obj return Wrapper bindings = globals().copy() bindings[obj.__class__.__name__] = WrapInstance(obj) return bindings class MyClass(ui.View): def __init__(self, ui_file, *args, **kwargs): ui.load_view(ui_file, pyui_bindings(self)) super().__init__(*args, **kwargs) if __name__ == '__main__': ui_file = 'MyPYUIForm.pyui' mc = MyClass(ui_file, name='MyForm') mc.present('sheet')
-
Quite interesting and still work after so many years. Just create a text file and rename it to Balistique.pyui and use the other as .py file. It works. May need to add a few close here and there but it is minor.