I've successfully created a bar chart. However, since there are so many glyphs right above and below each other, whenever a 'click' gets sent to TapTool, multiple glyphs have their tap tool trigger, so multiple images open in new tabs, instead of just a single 'closest to the click' glyph.
As you can see in the screenshots, at the default view, there are so many glyphs that are overlapping that if I simply click my mouse down, I'm likely hitting multiple glyphs, and this is why tap tool triggers multiple times (once per glyph hit). When zoomed in, it is obvious that rather than a bar chart, it is a scatter plot arranged to look like a bar chart, with each glyph representing a different image.
How do I force only the 'closest' glyph (or only take the first glyph from a list of glyphs hit by the taptool) to trigger its taptool?
zoomed_out
zoomed_in
There isn't much documentation online how to do this.
imgs = ['http://1...', 'http://2...', 'http://3...']
url = "#imgs"
click_tool = TapTool(callback=OpenURL(url=url))
p.add_tools(click_tool)
There are simply too many glyphs squeezed per pixel under your mouse cursor so they will all respond to your mouse click. A good solution would be to have a callback function attached to your plot's y-range that will re-draw the chart with less glyphs. So when you zoom in the outer glyphs will disappear from the plot and new one that were previously invisible will be added. At some zoom level you will not have to filter the glyphs anymore and they will all fit in. I mean something like this:
plot.y_range.callback = CustomJS(args=dict(source=source), code=code)
The plot canvas has a fixed height in pixels so in your callback you would need to put no more then that number of glyphs in your plot (number glyphs = canvas pixel height) This means one horizontal strip per pixel height. Then a single click should select just one glyph.
Related
I am drawing a pie chart out of provided data, and this can potentially get out of hand as the length of the labels can be pretty long, and there can be a lot of them overlapping each other. Because of this, it is crucial to find a good startangle parameter to my pie chart drawing.
Conceptually, I want to use a mouse scroll event to rotate the whole pie chart by 5 degrees every time the user uses the scroll wheel. Rotating the wedges isn't too much trouble with their theta1 and theta2 properties, but repositioning the labels and autotexts is serious trouble because of alignment properties and the lining up with wedges. I also want to retain an interactive frame rate, so clearing the figure and redrawing is not an option.
Here is one such situation where this is useful. The labels are too big and rotating the whole pie chart would help reposition them in sight. Of course in this case it would be enough to resize the chart instead of rotating but you get my point.
Is there a way to achieve this that does not imply rewriting the entire label and autotext positioning code for my own use?
In particular, I'm wondering if it wouldn't be possible to do something that is conceptually equivalent to making the same pyplot.pie call as before, only with a different startangle. Alternatively, maybe Text objects have methods I can use for positioning them around the newly rotated wedges that spare me working with just positions and sizes.
I am trying to use blitting with matplotlib to improve the performance of my plot, but am running into glitches after resizing the matplotlib window.
I have a matplotlib window showing 8 different lines plotted, along with a table of the values from the plot. The table and plot are updated every ~0.7 seconds.
Here is my main loop:
canvas.draw()
background = canvas.copy_from_bbox(ax.bbox)
while True
canvas.restore_region(background)
[update data for lines, table]
for line in lines:
ax.draw_artist(line)
canvas.blit(ax.bbox)
for line in lines:
line.set_animated(False)
canvas.draw_idle()
canvas.flush_events()
for line in lines:
line.set_animated(True)
time.sleep(0.7)
This updates as I expect it to, and everything looks OK.
But then when I resize the plot window (by clicking and dragging the window edge) I start to see artifacts left over from apparently as the resize was occurring (it appears for maybe 0.1 second, so I can't attach a screenshot). Basically I see what appears to be an old plot and the border of my table for a very short time on each iteration through my main loop, then the plot and table display fine.
What I've Tried:
I registered a callback for 'resize_event' and in that case do:
canvas.restore_region(firstBackground)
canvas.draw_idle()
canvas.flush_events()
Then on the next iteration through the main loop behave like normal. My callback is running, but this doesn't seem to help the problem at all.
So I think I need to do a transform on the background I saved before any data was plotted and call restore_region with the transformed background, but I am not sure if I am missing something simple.
EDIT:
Some more information. The plot I am trying to blit has two axes in the same area(one was created by calling twinx() on the other axis)
So far I have tried:
Calling set_visible(False) on all lines, then drawing and getting a new background image. This shows the same glitch on resize.
Calling cla() on the two axes that overlap. This also shows the same glitch after resizing.
I know that you can plot multiple figures in the same window by arranging them in a grid layout. However, I want to plot multiple figures, showing only one at a time and be able to move to the next or previous figure with buttons in the UI. I see arrows in the default UI which makes me think there's a way to do this.
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.
I have built a GUI with matplotlib and it contains several plots of values versus time. Now I need a special plot which just shows if a value is on or off (binary state).
Kinda like a control lamp on an analog control panel. I have 5 of those on/off values and I dont know how to do it the best way.
The "lamps" must be updateable because I stream the data from serial and analyze it in real time in my GUI.
I attached a picture where you see my current GUI. In the bottom right corner is now a bar chart, I tried to visualize the ON/OFF state with a bar, but it didn't work well and I wasn't able to animate it.
So yeah, how could I display 5 values with each an ON/OFF state in that area?
Instead of passing via bar charts I would directly plot a number of rectangles and then dynamically change their color.
You can find the documentation for rectangular patches here: http://matplotlib.org/api/patches_api.html#matplotlib.patches.Rectangle
If you need some pointers on how to animate such a patch have a look here:
https://nickcharlton.net/posts/drawing-animating-shapes-matplotlib.html