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.


    Putting a matplotlib.plot() image into a ui.ImageView?

    Pythonista
    4
    12
    14061
    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.
    • omz
      omz last edited by

      You can use savefig to save the plot to a file or file-like object. Here's an example using BytesIO:

      import matplotlib.pyplot as plt
      from io import BytesIO
      import ui
      
      plt.plot([1, 2, 3])
      
      b = BytesIO()
      plt.savefig(b)
      img = ui.Image.from_data(b.getvalue())
      
      img_view = ui.ImageView(background_color='white')
      img_view.content_mode = ui.CONTENT_SCALE_ASPECT_FIT
      img_view.image = img
      img_view.present()
      
      1 Reply Last reply Reply Quote 0
      • ccc
        ccc last edited by

        Awesome (as always). Thanks. Now, how do I make it pinchable, zoomable, scrollable?

        # coding: utf-8
        import matplotlib.pyplot as plt
        import io, math, ui
        
        # plt must be matplotlib.pyplot or its alias
        def plot_to_scrollable_image_view(plt):
            img_view = ui.ImageView()
            b = io.BytesIO()
            plt.savefig(b)
            img_view.image = ui.Image.from_data(b.getvalue())
            view = ui.ScrollView()
            view.add_subview(img_view)
            return view
        
        plt.plot([math.sin(x/10.0) for x in xrange(95)])
        plt.xlim(0, 94.2)
        view = plot_to_scrollable_image_view(plt)
        view.present()
        img_view = view.subviews[0]
        img_view.frame = view.bounds
        img_view.width *= 2
        img_view.height *= 2
        view.content_size = img_view.width, img_view.height
        

        EDIT: It is now draggable.

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

          How do I make it pinchable, zoomable?

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

            How do I make it pinchable, zoomable?

            There isn't really an easy way to do that, I'm afraid. Your best bet may be to save the image to a file, and then load it in a ui.WebView (a base64-encoded data: URL may also work, not sure about that right now).

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

              ccc, i think we really want a custom view which sets axis limits, which is relayed to plt, then a new image generated. not sure how slow that will be...

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

                @JonB I think it'd be better to PIL crop the previously-generated image.

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

                  that wont be feasible if you want to zoom, otherwise you lose resolution. as an example, somone here is trying to be able to zoom into one hour from a 24 hour graph. that means you'd need to generate the original image at over 2000 dpi in order to do a simple crop with a final rsolution of 92dpi. try that in matplotlib, it is very slow, plus you then need to read and write very large files, which will be slow as well.

                  here is a proof of concept of calling matplotlib repeatedly in the ui loop.
                  The key to responsiveness is to generate very low quality images in matplotlib (say, 16 dpi) while dragging, then replace this with a higher quality version once the dragging stops.

                  I'm working on a custom touch view, which will implement zooming, two finger scrolling, to see how this scales with more complex plots.

                  # coding: utf-8
                  import matplotlib.pyplot as plt
                  import io, math, ui
                  
                  
                  def plot_to_scrollable_image_view(plt):
                      img_view = ui.ImageView()
                      b = io.BytesIO()
                      plt.savefig(b,format='png',dpi=160)
                      img_view.image = ui.Image.from_data(b.getvalue())
                      view = ui.ScrollView()
                      view.add_subview(img_view)
                      view.dx=0
                      view.ready=True
                      view.bounces=False
                      return view
                  class delegate(object):
                     #@ui.in_background  #ui.delay called from backgrounded was unreliable.
                     def scrollview_did_scroll(self,sender):
                        ui.cancel_delays()
                        d = sender.content_offset[0]-5.0
                        sender.content_offset=(5,5)
                        sender.dx=d+sender.dx
                        def hq():
                          sender.ready=False
                          dx=sender.dx
                          sender.dx=0
                          xl=plt.xlim()
                          xl=[x+dx for x in xl]
                          plt.xlim(xl)
                          b = io.BytesIO()
                          plt.savefig(b,format='jpeg',dpi=160)
                          sender.subviews[0].image = ui.Image.from_data(b.getvalue())
                          sender.ready=True
                        if sender.ready:
                          sender.ready=False
                          dx=sender.dx
                          sender.dx=0
                          xl=plt.xlim()
                          xl=[x+dx for x in xl]
                          plt.xlim(xl)
                          b = io.BytesIO()
                          plt.savefig(b,format='jpeg',dpi=16)
                          sender.subviews[0].image = ui.Image.from_data(b.getvalue())
                          sender.ready=True
                        ui.delay(hq,0.2)
                  
                          
                  plt.plot([math.sin(x/10.0) for x in xrange(95)])
                  plt.xlim(0, 94.2)
                  view = plot_to_scrollable_image_view(plt)
                  view.present()
                  view.subviews[0].frame = view.bounds
                  view.subviews[0].x,view.subviews[0].y=(5,5)
                  view.content_size=tuple(view.subviews[0].bounds.size+(11,11))
                  view.content_offset=(5,5)
                  view.delegate=delegate()
                  
                  1 Reply Last reply Reply Quote 0
                  • ccc
                    ccc last edited by

                    Sweet! I updated a few things:

                    • raised the x limit from 95 to 950 to increase the content that can be scrolled through
                    • use subplots_adjust() to reduce white space around the graph
                    • reuse hq() to remove repeated lines
                    • reduce ui.delay to zero to make refresh more snappy. Are there downsides to this?

                    I not figure out how to stop the scrolling when the graph ends (i.e. below zero or above 950)

                    I will try to apply the lessons learned to SPLnFFT_Reader.py.

                    # coding: utf-8
                    import matplotlib.pyplot as plt
                    import io, math, ui
                    
                    def plot_to_scrollable_image_view(plt):
                        img_view = ui.ImageView()
                        b = io.BytesIO()
                        plt.savefig(b, format='png', dpi=160)
                        img_view.image = ui.Image.from_data(b.getvalue())
                        view = ui.ScrollView()
                        view.add_subview(img_view)
                        view.delegate = delegate()
                        view.dx = 0
                        view.ready = True;
                        view.bounces = False
                        return view
                    
                    class delegate(object):
                       #@ui.in_background  #ui.delay called from backgrounded was unreliable.
                       def scrollview_did_scroll(self,sender):
                          ui.cancel_delays()
                          sender.dx += sender.content_offset[0] - 5.0
                          sender.content_offset = (5, 5)
                          def hq(dpi=160):
                            sender.ready=False
                            dx, sender.dx = sender.dx, 0
                            plt.xlim([x + dx for x in plt.xlim()])
                            b = io.BytesIO()
                            plt.savefig(b, format='jpeg', dpi=dpi)
                            sender.subviews[0].image = ui.Image.from_data(b.getvalue())
                            sender.ready = True
                          if sender.ready:
                            hq(16)
                          ui.delay(hq, 0)
                    
                    plt.plot([math.sin(x/10.0) for x in xrange(950)])
                    plt.xlim(0, 95)  # approx 1 cycle of the sin wave
                    plt.subplots_adjust(left=0.06, bottom=0.05, right=0.98, top=0.97)
                    view = plot_to_scrollable_image_view(plt)
                    view.hidden = True  # wait until view is setup before displaying
                    view.present()
                    img_view = view.subviews[0]
                    img_view.frame = view.bounds
                    img_view.x, img_view.y = view.content_offset = (5, 5)
                    view.content_size = tuple(img_view.bounds.size + (11, 11))
                    view.hidden = False
                    
                    1 Reply Last reply Reply Quote 0
                    • ccc
                      ccc last edited by

                      Sweet! I updated a few things:

                      • raised the x limit from 95 to 950 to give us more content to scroll through
                      • use subplots_adjust() to reduce white space around the graph
                      • reuse hq() to remove repeated lines of code
                      • reduce ui.delay to zero to make refresh more snappy. Are there downsides to doing this?

                      I could not figure out how to stop the scrolling when the graph ends (i.e. below zero or above 950)

                      I will try to apply the lessons learned to SPLnFFT_Reader.py.

                      # coding: utf-8
                      import matplotlib.pyplot as plt
                      import io, math, ui
                      
                      def plot_to_scrollable_image_view(plt):
                          img_view = ui.ImageView()
                          b = io.BytesIO()
                          plt.savefig(b, format='png', dpi=160)
                          img_view.image = ui.Image.from_data(b.getvalue())
                          view = ui.ScrollView()
                          view.add_subview(img_view)
                          view.delegate = delegate()
                          view.dx = 0
                          view.ready = True;
                          view.bounces = False
                          return view
                      
                      class delegate(object):
                         #@ui.in_background  #ui.delay called from backgrounded was unreliable.
                         def scrollview_did_scroll(self,sender):
                            ui.cancel_delays()
                            sender.dx += sender.content_offset[0] - 5.0
                            sender.content_offset = (5, 5)
                            def hq(dpi=160):
                              sender.ready=False
                              dx, sender.dx = sender.dx, 0
                              plt.xlim([x + dx for x in plt.xlim()])
                              b = io.BytesIO()
                              plt.savefig(b, format='jpeg', dpi=dpi)
                              sender.subviews[0].image = ui.Image.from_data(b.getvalue())
                              sender.ready = True
                            if sender.ready:
                              hq(16)
                            ui.delay(hq, 0)
                      
                      plt.plot([math.sin(x/10.0) for x in xrange(950)])
                      plt.xlim(0, 95)  # approx 1 cycle of the sin wave
                      plt.subplots_adjust(left=0.06, bottom=0.05, right=0.98, top=0.97)
                      view = plot_to_scrollable_image_view(plt)
                      view.hidden = True  # wait until view is setup before displaying
                      view.present()
                      img_view = view.subviews[0]
                      img_view.frame = view.bounds
                      img_view.x, img_view.y = view.content_offset = (5, 5)
                      view.content_size = tuple(img_view.bounds.size + (11, 11))
                      view.hidden = False
                      
                      1 Reply Last reply Reply Quote 0
                      • JonB
                        JonB last edited by

                        in retrospect, the whole ready checking is not needed if we dont run in the background. my original attempt had long update times, so backgrounding was neded to keep ui responsive (basicslly adding up motion, then drawing when ready).

                        it isnt too hard to have the y axis scroll control "width".
                        also, it would be possible to stop once the xlim()[0] < 0, etc
                        we also need to properly scale screen width to xlim axes, so finger motion tracks correctly.

                        howver, this approach did not scale well to a plot with 600000 points, since even a low dpi plot took many seconds to generate.
                        i think we need to actually generate a new plot each time, only plotting the portion of data within the view, and reducing the amount of data points for large plots. i.e if zoomed out, resample data to an appropriate number of points.

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