When i plot something in PyCharm using matplotlib it plots the figure in a seperate window, which is what i want, but it also opens it on the main monitor. Is there a option to open it on my second monitor?
I could not find any similar question (only questions about plotting on the same monitor without a seperate window).
Thanks in advance!
You can specify a position in the code
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
mngr = plt.get_current_fig_manager()
# to put it into the upper left corner for example:
mngr.window.setGeometry(2000,100,640, 545)
plt.show()
I have found a solution in another post How do you set the absolute position of figure windows with matplotlib?
def move_figure(f, x, y):
"""Move figure's upper left corner to pixel (x, y)"""
backend = matplotlib.get_backend()
if backend == 'TkAgg':
f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))
elif backend == 'WXAgg':
f.canvas.manager.window.SetPosition((x, y))
else:
# This works for QT and GTK
# You can also use window.setGeometry
f.canvas.manager.window.move(x, y)
f, ax = plt.subplots()
move_figure(f, 2200, 500)
plt.show()
Related
I am using the mpldatacursor library to click on points on a scatter plot and store the values into a list for later use. This seems to be working fine but I also would like to see the values that I've clicked on in the scatter plot. I am using this example from Stack Overflow: Python: get corresponding information of data points interactively with mouse. The problem I am having is that the values shown when clicked are unreadable and sometimes doesn't even show up. The code is roughly the same as shown in the example, so I don't understand why, in my case, the values aren't readable. Could this possibly be because I need to update matplotlib to the newest version? Any suggestions would be appreciated.
import matplotlib.pyplot as plt
from mpldatacursor import datacursor
%matplotlib nbagg
import random
import numpy as np
fig, ax = plt.subplots()
ax.set_title('Double click on a dot to display its label')
# Plot a number of random dots
for i in range(1, 1000):
x = random.random()
y = random.random()
ax.scatter([x], [y], label='ID: X: {:.4f},Y: {:.4f}'.format(x,y))
# Use a DataCursor to interactively display the label for a selected line...
datacursor(formatter='{label}'.format)
coords = []
def onclick(event):
global ix, iy
ix, iy = event.xdata, event.ydata
print ('x = %d, y = %d'%(
ix, iy))
global coords
coords.append((ix, iy))
if len(coords) == 3:
fig.canvas.mpl_disconnect(cid)
return coords
cid = fig.canvas.mpl_connect('button_press_event', onclick)
New to the forum!
I’m trying to create an interactive barchart for a homework problem – I am wondering where I am going wrong with out using some one else's solution (like this awesome code here!)
I click on the chart to generate a reference line with a new y value and to change the color of the bar. For simplicity, I’m debugging using just two colors and comparing to the mean (when y >mean, y<mean). Aside from the two codes below, I've tried to clear the chart and re-draw it within the onclick function and to write a separate function, although not sure how to call it... Any guidance would be much appreciated - I'm not sure how the pieces fit together, so its hard to break it down for troubleshooting.
df=pd.DataFrame({'mean':[40000,50000,20000,60000,3000],'CI':[4000,4000,3000,1000,200]},index=['A','B','C','D','E'])
df=df.T
fig, ax = plt.subplots()
bars = ax.bar([1,2,3,4,5], df.loc['mean'])
#Set horizontal line
hline = ax.axhline(y=20000, c='red', linestyle='--')
ax.set_xticks([1,2,3,4,5])
ax.set_xticklabels(df.columns)
def onclick(event):
hline.set_ydata([event.ydata, event.ydata])
df2.loc['y']=event.ydata
for val in df2.loc['y']:
if df2.loc['y'] < df2.loc['mean']:
col.append('red')
else:
col.append('white')
fig.canvas.mpl_connect('button_press_event', onclick)
Also tried
def onclick(event):
#provide y data, based on where clicking. Note to self: 'xdata' would give slanted line
hline.set_ydata([event.ydata, event.ydata])
df2.loc['y']=event.ydata
for bar in bars:
if event.ydata < df2.loc['mean']:
bar.set_color('red')
else:
bar.set_color('white')
return result
The main problem is that you never redraw the canvas, so every change you communicate to matplotlib will not appear in the figure generated by the backend. You also have to update the properties of the rectangles representing the bars - you tried this with bar.set_color() in one of the versions which changes both facecolor and edgecolor, intended or otherwise.
import pandas as pd
import matplotlib.pyplot as plt
df=pd.DataFrame({'mean':[40000,50000,20000,60000,3000],'CI':[4000,4000,3000,1000,200]},index=['A','B','C','D','E'])
df2=df.T
fig, ax = plt.subplots()
bars = ax.bar(range(df2.loc['mean'].size), df2.loc['mean'])
#Set horizontal line
hline = ax.axhline(y=20000, c='red', linestyle='--')
ax.set_xticks(range(df2.columns.size), df2.columns)
def onclick(event):
#update hline position
hline.set_ydata([event.ydata])
#change all rectangles that represent the bars
for bar in bars:
#retrieve bar height and compare
if bar.get_height() > event.ydata:
#to set the color
bar.set_color("red")
else:
bar.set_color("white")
#redraw the figure to make changes visible
fig.canvas.draw_idle()
fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
output:
I am using matplotlib FuncAnimation to display data received in real time from sensors.
Before updating matplotlib, I was using matplotlib 3.2.2 and the behavior was the following:
graph opens and autoscales axes limits to the data coming in from the sensors
if I interactively zoom in on the graph, the graph remains on the zone I defined (which is convenient to inspect a small portion of the data).
Now, after updating to matplotlib 3.3.4, the graph still opens and autoscales, but if I zoom in on the data, the graph immediately autoscales back to the full extent of the data, which makes inspecting impossible.
I have tried setting the axes autoscaling to False, but then the graph does not autoscale at all, which is not convenient.
I have put below some example code that reproduces the phenomenon described above:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from random import random
def test():
"""Test live matplotlib animation"""
fig, (ax1, ax2) = plt.subplots(2, 1)
# for ax in ax1, ax2: # uncomment to deactivate autoscale
# ax.set_autoscale_on(False)
def animate(i):
x = random() # Fake sensor 1
y = 10 * random() + 3 # Fake sensor 2
ax1.plot(i, x, 'ok')
ax2.plot(i, y, 'ob')
ani = FuncAnimation(fig, animate, interval=100)
plt.show()
return ani
Any idea how to get back to the initial behavior while keeping a more recent version of matplotlib? Thank you very much in advance!
The workaround I have found is to use a callback to mouse events on the matplotlib figure, that deactivates autoscaling when there is a left click, and reactivates them with a right click.
def onclick(event):
ax = event.inaxes
if ax is None:
pass
elif event.button == 1: # left click
ax.set_autoscale_on(False)
elif event.button == 3: # right click
ax.set_autoscale_on(True)
else:
pass
cid = fig.canvas.mpl_connect('button_press_event', onclick)
This means that whenever the user uses interactive zooming in some axes of the figure (by left clicking to define the zoom area), autoscaling is deactivated and inspection, panning etc. are possible. The user then just has to right-click to go back to the autoscaled, real-time data.
In fact this is even better than what I had initially in matplotlib 3.2.2, where once zooming was done, there was no way to go back to autoscaled axes limits.
I would like to plot the movement of the mouse in near real-time using matplotlib and pynput, but I suspect I am getting some issues with the code being blocked. Code is uses a simplified version of this answer.
import matplotlib.pyplot as plt
from pynput import mouse
from time import sleep
fig, ax = plt.subplots()
ax.set_xlim(0, 1920-1)
ax.set_ylim(0, 1080-1)
plt.show(False)
plt.draw()
x,y = [0,0]
points = ax.plot(x, y, 'o')[0]
# cache the background
background = fig.canvas.copy_from_bbox(ax.bbox)
def on_move(x, y):
points.set_data(x,y)
# restore background
fig.canvas.restore_region(background)
# redraw just the points
ax.draw_artist(points)
# fill in the axes rectangle
fig.canvas.blit(ax.bbox)
with mouse.Listener(on_move=on_move) as listener:
sleep(10)
The code seems to halt on ax.draw_artist(points). The pynput mouse listener is a threading.Thread, and all callbacks are invoked from the thread. I am not familiar enough with the inner workings of matplotlib or threading to determine what is the cause.
It might cause problems to run a thread with GUI input in parallel with the matplotlib GUI.
In any case, it could make more sense to use matplotlib tools only. There is an event handling mechanism available, which provides a "motion_notify_event" to be used to obtain the current mouse position. A callback registered for this event would then store the mouse position and blit the updated points.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_xlim(0, 1920-1)
ax.set_ylim(0, 1080-1)
x,y = [0], [0]
# create empty plot
points, = ax.plot([], [], 'o')
# cache the background
background = fig.canvas.copy_from_bbox(ax.bbox)
def on_move(event):
# append event's data to lists
x.append(event.xdata)
y.append(event.ydata)
# update plot's data
points.set_data(x,y)
# restore background
fig.canvas.restore_region(background)
# redraw just the points
ax.draw_artist(points)
# fill in the axes rectangle
fig.canvas.blit(ax.bbox)
fig.canvas.mpl_connect("motion_notify_event", on_move)
plt.show()
I'm doing some 3D surface plots using Matplotlib in Python and have noticed an annoying phenomenon. Depending on how I set the viewpoint (camera location), the vertical (z) axis moves between the left and right side. Here are two examples: Example 1, Axis left, Example 2, Axis right. The first example has ax.view_init(25,-135) while the second has ax.view_init(25,-45).
I would like to keep the viewpoints the same (best way to view the data). Is there any way to force the axis to one side or the other?
I needed something similar: drawing the zaxis on both sides. Thanks to the answer by #crayzeewulf I came to following workaround (for left, righ, or both sides):
First plot your 3d as you need, then before you call show() wrap the Axes3D with a Wrapper class that simply overrides the draw() method.
The Wrapper Class calls simply sets the visibility of some features to False, it draws itself and finally draws the zaxis with modified PLANES. This Wrapper Class allows you to draw the zaxis on the left, on the rigth or on both sides.
import matplotlib
matplotlib.use('QT4Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
class MyAxes3D(axes3d.Axes3D):
def __init__(self, baseObject, sides_to_draw):
self.__class__ = type(baseObject.__class__.__name__,
(self.__class__, baseObject.__class__),
{})
self.__dict__ = baseObject.__dict__
self.sides_to_draw = list(sides_to_draw)
self.mouse_init()
def set_some_features_visibility(self, visible):
for t in self.w_zaxis.get_ticklines() + self.w_zaxis.get_ticklabels():
t.set_visible(visible)
self.w_zaxis.line.set_visible(visible)
self.w_zaxis.pane.set_visible(visible)
self.w_zaxis.label.set_visible(visible)
def draw(self, renderer):
# set visibility of some features False
self.set_some_features_visibility(False)
# draw the axes
super(MyAxes3D, self).draw(renderer)
# set visibility of some features True.
# This could be adapted to set your features to desired visibility,
# e.g. storing the previous values and restoring the values
self.set_some_features_visibility(True)
zaxis = self.zaxis
draw_grid_old = zaxis.axes._draw_grid
# disable draw grid
zaxis.axes._draw_grid = False
tmp_planes = zaxis._PLANES
if 'l' in self.sides_to_draw :
# draw zaxis on the left side
zaxis._PLANES = (tmp_planes[2], tmp_planes[3],
tmp_planes[0], tmp_planes[1],
tmp_planes[4], tmp_planes[5])
zaxis.draw(renderer)
if 'r' in self.sides_to_draw :
# draw zaxis on the right side
zaxis._PLANES = (tmp_planes[3], tmp_planes[2],
tmp_planes[1], tmp_planes[0],
tmp_planes[4], tmp_planes[5])
zaxis.draw(renderer)
zaxis._PLANES = tmp_planes
# disable draw grid
zaxis.axes._draw_grid = draw_grid_old
def example_surface(ax):
""" draw an example surface. code borrowed from http://matplotlib.org/examples/mplot3d/surface3d_demo.html """
from matplotlib import cm
import numpy as np
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm, linewidth=0, antialiased=False)
if __name__ == '__main__':
fig = plt.figure(figsize=(15, 5))
ax = fig.add_subplot(131, projection='3d')
ax.set_title('z-axis left side')
ax = fig.add_axes(MyAxes3D(ax, 'l'))
example_surface(ax) # draw an example surface
ax = fig.add_subplot(132, projection='3d')
ax.set_title('z-axis both sides')
ax = fig.add_axes(MyAxes3D(ax, 'lr'))
example_surface(ax) # draw an example surface
ax = fig.add_subplot(133, projection='3d')
ax.set_title('z-axis right side')
ax = fig.add_axes(MyAxes3D(ax, 'r'))
example_surface(ax) # draw an example surface
plt.show()
As pointed out in a comment below by OP, the method suggested below did not provide adequate answer to the original question.
As mentioned in this note, there are lots of hard-coded values in axis3d that make it difficult to customize its behavior. So, I do not think there is a good way to do this in the current API. You can "hack" it by modifying the _PLANES parameter of the zaxis as shown below:
tmp_planes = ax.zaxis._PLANES
ax.zaxis._PLANES = ( tmp_planes[2], tmp_planes[3],
tmp_planes[0], tmp_planes[1],
tmp_planes[4], tmp_planes[5])
view_1 = (25, -135)
view_2 = (25, -45)
init_view = view_2
ax.view_init(*init_view)
Now the z-axis will always be on the left side of the figure no matter how you rotate the figure (as long as positive-z direction is pointing up). The x-axis and y-axis will keep flipping though. You can play with _PLANES and might be able to get the desired behavior for all axes but this is likely to break in future versions of matplotlib.