omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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.


    Not sure how to combine ui with a plot

    Pythonista
    4
    16
    2778
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • rewire88
      rewire88 last edited by

      New to Pythonista and I'm trying to make a script with a bank of sliders that control gains on a separate plot (gain vs. frequency). I've played around with 'ui' and I've got basic controls to work, but I'm lost as far as adding a plot to the ui. The plot should change in real time as the sliders are adjusted, like a graphic EQ in audio.

      Is this possible? Can someone point me to the general method I need to use, which modules I need to study, etc.

      1 Reply Last reply Reply Quote 0
      • JonB
        JonB last edited by

        You will want to implement a custom ui.View subclass, which has a draw method. Draw will be called at 60 fps by default, and you would use the path drawing tools.

        If you wanted a matplotlib plot, then there are some other techniques to get something that draws quickly.

        1 Reply Last reply Reply Quote 1
        • rewire88
          rewire88 last edited by

          Great, that's what I was looking for - just needed a foothold. Thanks!

          1 Reply Last reply Reply Quote 0
          • JonB
            JonB last edited by

            Are you wanting to use matplotlib? Or you are okay scaling your axes and determining the x/y screen coordinates directly(and using line_to, etc)?

            For matplotlib, it is possible to use backend_pythonista
            https://github.com/jsbain/backend_pythonista
            Likely, it would be slow, since rendering is not that fast. There are some techniques that could be used to do fast updates with jpg, which does render faster, then switch to png after a delay.

            Alternatively,
            https://github.com/jsbain/pythonista_matplotlib_backports
            Allows one to use matplotlib html rendering, which I think in theory would allow integrated plot controls and everything to work inside a browser. This enables animations for example.
            Likely not all of the current version matplotlib controls will work, so would need to be backported -- which is maybe easier than it sounds. The idea of the backports module was to allow the pure pythons bits if newer matplotlib to be used with pythonista's matplotlib version -- I did animations, but other pull requests are welcome!

            1 Reply Last reply Reply Quote 1
            • rewire88
              rewire88 last edited by

              Scaling and figuring out coordinates isn't the problem at all, but it's my lack of basic Python knowledge about classes, inheritance, etc. I'm just trying to start with something simple - move a slider up and down and have a shape on the screen follow along.

              After your hint I'm able to move an oval up and down. Very crude but it's a start.

              import ui
              import math
              
              ss=100
              
              class testDrawClass(ui.View):
              	def __init__(self):
              		self.height, self.width=400,400
              		self.background_color='white'
              		
              	
              	def draw(self):
              		global ss
              		path = ui.Path.oval(100,ss,75,30)
              		ui.set_color('red')
              		path.fill()
              		
              	def ss_slide(self,sender):
              		global ss
              		ss=(400*sender.value)
              		ss=math.floor(ss)
              		print("ss:",ss)
              		self.set_needs_display()
              
              u = testDrawClass()	
              slider = ui.Slider()		
              slider.action=u.ss_slide
              slider.center=(200,200)
              u.add_subview(slider)
              	
              u.present('sheet')
              cvp 1 Reply Last reply Reply Quote 0
              • cvp
                cvp @rewire88 last edited by cvp

                @rewire88 very good start.

                Some advices:

                • create your slider in view initialization
                • avoid global in such cases
                • avoid too much print, see here view's name used as title

                For info, a method called each update_interval second (default 1/60) is update

                Example with oval height changing

                import ui
                import math
                
                class testDrawClass(ui.View):
                    def __init__(self):
                        self.height, self.width=400,400
                        self.background_color='white'
                        
                        self.slider = ui.Slider()        
                        self.slider.action=self.ss_slide
                        self.slider.center=(200,200)
                        self.add_subview(self.slider)
                        
                    def draw(self):
                        path = ui.Path.oval(100,100,75,30*self.slider.value)
                        ui.set_color('red')
                        path.fill()
                        
                    def ss_slide(self,sender):
                        self.name = f"ss:{sender.value}"
                        self.set_needs_display()
                
                u = testDrawClass() 
                u.present('sheet')
                
                1 Reply Last reply Reply Quote 1
                • rewire88
                  rewire88 last edited by

                  Thanks, very helpful. I was sure global variables weren't the right way to go but didn't know how to do it correctly.

                  I still have all sorts of questions and things to explore. My biggest puzzle is how to organize things. I'm going to have multiple controls. Probably 5 sliders controlling different different gain/frequency graphs. (Set Left channel gains, flip switch and use same sliders to set Right channel gains, etc.) Then a bunch of simple selectors for things like noise reduction ON/OFF. I'll probably make a rough draft with everything in one big class to get it to work then refactor.

                  cvp 1 Reply Last reply Reply Quote 0
                  • cvp
                    cvp @rewire88 last edited by

                    @rewire88 We could help you but the best way to learn is to do it yourself, and, of course we could help after for debugging for instance

                    1 Reply Last reply Reply Quote 1
                    • rewire88
                      rewire88 last edited by

                      I've been working on this and I have basically what I wanted with the graphs and controls. There are still some fundamental things I don't understand though with how 'View' works.

                      My graph has axes and labels that indicate frequency and dB level. It seems inefficient to draw these static elements each time when they don't change at all. I tried a few things, but found out I need to put statements in the draw() method to get them to work. I'm not sure why this is. Is this just the way it works and all 'static' graphics like this are always part of the draw() everytime? Is there a better standard practice approach to this type of problem?

                      Here's a simplified example, using the previous code from this thread, showing my ignorance. The black oval would be analogous to the graph axes and labels that aren't changing.

                      import ui
                      
                      class testClass(ui.View):
                              def __init__(self):
                                      self.height,self.width=400,400
                                      self.background_color='white'
                      
                                      self.slider=ui.Slider()
                                      self.slider.center=(200,200)
                                      self.slider.action=self.ss_slide
                                      self.add_subview(self.slider)
                                      path2=ui.Path.oval(300,100,75,30)
                                      ui.set_color('black')
                                      path2.fill() # wondering why this doesn't get drawn
                      
                              def draw(self):
                                      path=ui.Path.oval(100,100,75,30*self.slider.value)
                                      ui.set_color('orange')
                                      path.fill()
                      
                      
                              def ss_slide(self,sender):
                                      self.set_needs_display()
                      
                      u=testClass()
                      u.present('sheet')
                      
                      
                      cvp 1 Reply Last reply Reply Quote 0
                      • cvp
                        cvp @rewire88 last edited by

                        @rewire88 try this

                        import ui
                        
                        class testClass(ui.View):
                                def __init__(self):
                                        self.height,self.width=400,400
                                        self.background_color='white'
                        
                                        self.slider=ui.Slider()
                                        self.slider.center=(200,200)
                                        self.slider.action=self.ss_slide
                                        self.add_subview(self.slider)
                                        
                                        # draw your black oval as background of your ui.View
                                        iv = ui.ImageView()
                                        iv.frame = self.frame
                                        self.add_subview(iv)
                                        with ui.ImageContext(self.width,self.height) as ctx:
                                          path2=ui.Path.oval(300,100,75,30)
                                          ui.set_color('black')
                                          path2.fill() 
                                          iv.image = ctx.get_image()
                        
                                def draw(self):
                                        path=ui.Path.oval(100,100,75,30*self.slider.value)
                                        ui.set_color('orange')
                                        path.fill()
                        
                        
                                def ss_slide(self,sender):
                                        self.set_needs_display()
                        
                        u=testClass()
                        u.present('sheet')
                        
                        1 Reply Last reply Reply Quote 1
                        • rewire88
                          rewire88 last edited by

                          Thanks, this is basically what I was looking for. I'll have to study the context stuff to understand what it's doing.

                          I still have a problem with having this in the 'background' though. I moved the static black oval to the same position as the slider controlled orange oval, and the black oval is always in front. I tried adding 'bring_to_front' and 'send_to_back' a few different places for the u and iv images, but no luck. What am I missing?

                          cvp 1 Reply Last reply Reply Quote 0
                          • cvp
                            cvp @rewire88 last edited by cvp

                            @rewire88 said

                            I moved the static black oval to the same position as the slider controlled orange oval,

                            Why did you do that? If you have a drawing that does not vary, put it in the init.

                            The path drawn in The draw method is in the self view and the ui.ImageView is a subview, thus always in front of the self view it-self.

                            You could use two ImageViews, one for the black oval and another for the orange varying oval.
                            As they are both subviews of the self view, you can decide which one is in front or at back

                            1 Reply Last reply Reply Quote 1
                            • rewire88
                              rewire88 last edited by

                              When I said I moved the black oval I meant I moved it's location on the screen to test this front back thing - I left the code in the init. Unclear language on my part.

                              Thanks for your help. I'm slowly getting a better understanding of views and images.

                              1 Reply Last reply Reply Quote 0
                              • JonB
                                JonB last edited by

                                Another approach would be to have two views that are the same size, that both use draw() -- but only call set_needs_display on the view that needs update. So, it you have 4 graphs, each controlled by different sliders, you'd only need to update the changed views.

                                The thing you have to be careful with using an image view is whether you redraw the image when the view gets resized (say, when orientation changes). You can use a layout method to handle size changes, and regenerate the image if needed. Multiple views with different draw methods would get draw called automatically upon resize.

                                1 Reply Last reply Reply Quote 2
                                • mikeno
                                  mikeno last edited by

                                  Hi, this is OK but the black circle is over the red one, how can we put the black circle behind as background?

                                  1 Reply Last reply Reply Quote 0
                                  • mikeno
                                    mikeno last edited by

                                    You wrote * Another approach would be to have two views that are the same size, that both use draw() -- but only call set_needs_display on the view that needs update. *
                                    Could you please show an example?

                                    1 Reply Last reply Reply Quote 0
                                    • First post
                                      Last post
                                    Powered by NodeBB Forums | Contributors