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?
-
matplotlib.plot() puts an image onto the Pythonista console. I can tap and hold on that image to copy it to the clipboard, etc.
Is there a programmatic way (image_context, etc.) to move a plot() image into a ui.ImageView?
-
show() puts it in the console... iirc there is another option which copies an image.
-
You can use
savefig
to save the plot to a file or file-like object. Here's an example usingBytesIO
: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()
-
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.
-
How do I make it pinchable, zoomable?
-
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-encodeddata:
URL may also work, not sure about that right now). -
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...
-
@JonB I think it'd be better to PIL crop the previously-generated image.
-
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()
-
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
-
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
-
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.