Updating matplotlib annotation position without redrawing all data - python

I've essentially recreated the matlab data cursor on my custom pyplot figure tool. It allows dragging and left/right arrow key presses to update an annotation box's position along a line, etc. My problem comes from when there are a large number of lines. The data cursor still works, but dragging its position around is a nightmare and is extremely slow. The problem stems, I'm rather certain, from my canvas.draw() calls since it's redrawing the entire figure every time the data cursor moves.
I'm working towards implementing options like blit() and "restore_region()", but I'm having great difficulty implementing them. Can anyone provide even a shell of how to make this work? I can't include actual code due to the classification of the code itself, but I can give a small pseudo-code example.
def create_anno(self):
# Do annotation creation stuff here
self.background = self.figure.canvas.copy_from_bbox(self.axis.bbox)
def key_press(self):
self.figure.canvas.restore_region(self.background)
# Do update position/text stuff here
self.figure.canvas.blit(self.axis.bbox)
self.figure.canvas.flush_events()
The key_press function is called normally, but the annotation box doesn't move anymore. Most examples use draw_artist() between their restore_region() and blit() calls, but annotation doesn't have that. I just want to update a little annotation box's position across a figure without having to redraw the entire axis. Any ideas?
Thanks!

Related

Is there any way I can duplicate a QOpenGLWidget without redrawing it?

I have a high poly object drawn in a QOpenGLWidget, and I want to do a feature so when I press a key this widget is drawn on another window, and the other one to be left in the back (so still visible). So I would need a way to duplicate it, without redrawing, because redrawing a high poly object takes time. Is there a way I can achieve this?

Force update to matplotlib toolbar/status bar information

I've got a matplotlib plot in which I've modified the toolbar/status bar information much as in this question. This works just fine and the necessary information is displayed as I need it.
However, I often need to update the plot (which is done by calling draw() on the canvas object) with new data. What happens though is that the toolbar/status bar information won't update until the mouse is moved again. I'd like this information to update as soon as the canvas is redrawn because some of this information is pertinent to the new plot.
My attempt at making this happen was to force a mouse motion event to trigger. I've tried to trigger the event from the canvas via self.canvas.motion_notify_event(0,0) but that doesn't seem to work well. I can see that the toolbar itself has a mouse_move method, but I don't know how to trigger it (or even if it's what I want to trigger).
How can I force the toolbar/status bar information to update during a plot redraw without requiring the user to move their mouse slightly?
Did a lot of digging and figured it out. The motion_notify_event method was what I was looking for, I was just using it wrong. You can fake matplotlib out and trigger a MouseEvent by using this function, which will then make matplotlib call all functions that respond to MouseEvents, including updating the toolbar/status bar information.
The key here is that I needed to trigger the MouseEvent as if it happened within the axes object, not the entire figure. The input to the function is the (x,y) pixel position of the event with respect to the lower left corner of the figure window. By using (0,0) as I did in my question, I was saying the mouse event happened at the lower left corner of the figure window, not on the axes itself. Matplotlib does not show toolbar/status bar information unless the cursor is on the axes.
What you can do then is pick some random pixel position on the axes and use that as the position. A simple way to pick such a pixel position is using matplotlib transformations.
The following now works for me:
canvas.motion_notify_event(*ax.transAxes.transform([0,0]))
Of course in my case I'm not displaying the data coordinates of the mouse, so your use cases may vary.

2D Game Engine - Implementing a Camera

So I've been making a game using Python, specifically the PyGame module. Everything has been going fairly well (except Python's speed, am I right :P), and I've got a nice list of accomplishments from this, but I just ran into a... speedbump. Maybe a mountain. I'm not to sure yet. The problem is:
How do I go about implementing a Camera with my current engine?
That probably means nothing to you, though, so let me explain what my current engine is doing: I have a spritesheet that I use for all images. The map is made up of a double array of Tile objects, which fills up the display (800 x 640). The map also contains references to all Entity's and Particles. So now I want to create a a camera, so that the map object can be Larger than the display. To do this I've devised that I'll need some kind of camera that follows the player (with the player at the center of the screen). I've seen this implemented before in games, and even read a few other similar posts, but I need to also know Will I have to restructure all game code to work this in? My first attempt was to make all object move on the screen when the player moves, but I feel that there is a better way to do this, as this screws up collision detection and such.
So, if anyone knows any good references to problems like this, or a way to fix it, I'm all ears... er.. eyes.
Thanks
You may find this link to be of interest.
In essence, what you need to do is to distinguish between the "actual" coordinates, and the "display" coordinates of each object.
What you would do is do the bulk of the work using the actual coordinates of each entity in your game. If it helps, imagine that you have a gigantic screen that can show everything at once, and calculate everything as normal. It might help if you also designed the camera to be an entity, so that you can update the position of your camera just like any other object.
Once everything is updated, you go to the camera object, and determine what tiles, objects, particles, etc. are visible within the window, and convert their actual, world coordinates to the pixel coordinates you need to display them correctly.
If this is done correctly, you can also do things like scale and otherwise modify the image your camera is displaying without affecting gameplay.
In essence, you want to have a very clear distinction between gameplay and physics logic/code, and your rendering/display code, so your game can do whatever it wants, and you can render it however you want, with minimal crossover between the two.
So the good news is, you probably don't need to change anything about how your game itself works. The bad news is, you'll probably have to go in and rewrite your rendering/drawing code so that everything is drawn relative to the camera, not to the world.
Since I can't have a look into your code, I can't assess how useful this answer will be for you.
My approach for side scroller, moveable maps, etc. is to blit all tiles onto a pygame.Surface spanning the dimensions of the whole level/map/ etc. or at least a big chunk of it. This way I have to blit only one surface per frame which is already prepared.
For collision detection I keep the x/y values (not the entire rect) of the tiles involved in a separate list. Updating is then mainly shifting numbers around and not surfaces anymore.
Feel free to ask for more details, if you deem it useful :)

Python tkinter move

I have numerous lines on a graph, datalines, horizontal divider line, day and month indicator lines, etc. I have typically, probably until now, been adding lines in this fashion:
canvas_1.create_line(x1,y1,x2,y2)
I now have simple problem. I want to also add in a vertical scale to show how much above and below zero the line is...quite natural. The datafile is big, roughly 12,000 data units and growing so I have everything setup using the left and right arrow keys to allow scrolling through the data. It works great but I haven't added in the vertical scale yet. Right now everything moves:
canvas_1.move(ALL,x,y)
When I add in the vertical scale I don't want the vertical scale to move. I know by using the move(ALL) that the vertical scale will also move.
What do I have to change in order to get the vertical scale so it won't move? Do I have to go out and 'label' all create_line statements???
line1 = canvas_1.create_line....
If so when I have mega reference of lines that I want to move how do I put them into the move statement. Do I have to put all the labels into a list or what? I kinda really lost in the thought process on this concept.
I fess I'm still looking into scrollbars but haven't quite had any good luck with them yet and I have a feeling I will still have the same problem to be dealt with.
The move method takes either an id of a single object, or a tag that represents zero or more objects. ALL is a built-in tag (literally, the string "all") that refers to everything on the canvas.
So, to use the move method without moving the scale, give everything except the scale a unique tag, and then use that tag for the move command.
canvas_1.create_line(x1,y1,x2,y2, tags=("lines",))
...
canvas_1.move("lines")
For more information about tags, see http://effbot.org/tkinterbook/canvas.htm#item-specifiers

python matplotlib blit to axes or sides of the figure?

I'm trying to refresh some plots that I have within a gui everytime I go once through a fitting procedure. Also, these plots are within a framw which can be resized, so the axes and labels etc need to be redrawn after the resizing. So was wondering if anyone knew how to update the sides of a figure using something like plot.figure.canvas.copy_from_bbox and blit. This appears to only copy and blit the background of the graphing area (where the lines are being drawn) and not to the sides of the graph or figure (where the labels and ticks are). I have been trying to get my graphs to update by trial and error and reading mpl documentation, but so far my code has jst become horrendously complex with things like self.this_plot.canvas_of_plot..etc.etc.. .plot.figure.canvas.copy_from_bbox... which is probably far too convoluted.
I know that my language might be a little off but I've been trying to read through the matplotlb documentation and the differences between Figure, canvas, graph, plot, figure.Figure, etc. are starting to evade me. So my first and foremost question would be:
1 - How do you update the ticks and labels around a matplotlib plot.
and secondly, since I would like to have a better grasp on what the answer to this question,
2 - What is the difference between a plot, figure, canvas, etc. in regards to the area which they cover in the GUI.
Thank you very much for the help.
All this can certainly be rather confusing at first!
To begin with, if you're chaining the ticks, etc, there isn't much point in using blitting. Blitting is just a way to avoid re-drawing everything if only some things are changing. If everything is changing, there's no point in using blitting. Just re-draw the plot.
Basically, you just want fig.canvas.draw() or plt.draw()
At any rate, to answer your first question, in most cases you won't need to update them manually. If you change the axis limits, they'll update themselves. You're running into problems because you're blitting just the inside of the axes instead of redrawing the plot.
As for your second question, a good, detailed overview is the Artist Tutorial of the Matplotlib User's Guide.
In a nutshell, there are two separate layers. One deals with grouping things into the parts that you'll worry about when plotting (e.g. the figure, axes, axis, lines, etc) and another that deals with rendering and drawing in general (the canvas and renderer).
Anything you can see in a matplotlib plot is an Artist. (E.g. text, a line, the axes, and even the figure itself.) An artist a) knows how to draw itself, and b) can contain other artists.
For an artist to draw itself, it uses the renderer (a backend-specific module that you'll almost never touch directly) to draw on a FigureCanvas a.k.a. "canvas" (an abstraction around either a vector-based page or a pixel buffer). To draw everything in a figure, you call canvas.draw().
Because artists can be groups of other artists, there's a hierarchy to things. Basically, something like this (obviously, this varies):
Figure
Axes (0-many) (An axes is basically a plot)
Axis (usually two) (x-axis and y-axis)
ticks
ticklabels
axis label
background patch
title, if present
anything you've plotted, e.g. Line2D's
Hopefully that makes things a touch clearer, anyway.
If you really do want to use blitting to update the tick labels, etc, you'll need to grab and restore the full region including them. This region is a bit tricky to get, because it isn't exactly known until after draw-time (rendering text in matplotlib is more complicated than rendering other things due to latex support, etc). You can do it, and I'll be glad to give an example if it's really what you want, but it's typically not going to yield a speed advantage over just drawing everything. (The exception is if you're only updating one subplot in a figure with lots of subplots.)

Categories

Resources