-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unable to scale figure with Netgraph on PyQt window #46
Comments
That is the expected/desired behaviour. You can suppress warnings easily, though; some options are listed here. |
This warnings are not much of a problem, I just mentioned cause I thought it could help in the issue somehow, since it mention about the axis limit and it is something configurable in MatPlotLib. I tried to change this config using the steps mentioned here, but with no success either. |
The axis is tight around the graph. You can tell by the clipping of the node artists but you can confirm it if you turn the axis frame back on ( The axis is square, as netgraph by default enforces an equal x and y aspect, and the default The figure is a rectangle as you are setting them to be rectangular: figure.set_figheight(8)
figure.set_figwidth(6) If you want the figure to be tight around the axis, you either need to make the figure square, or you need to make the scale rectangular (e.g. |
Just to be perfectly clear, you should get a tighter fit with: self.graph = EditableGraph([(0, 1)], scale=(1.5, 2), ax=self.ax) |
I also want to make the figure tight with the QtWindow. Even if the figure is squared, it still have this space where I can not stretch the drawing of the graph. import sys
import matplotlib; matplotlib.use("Qt5Agg")
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
from netgraph import EditableGraph
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
figure = plt.figure()
figure.set_figheight(6)
figure.set_figwidth(6) #changed here, now it is squared
plt.tight_layout()
figure.patch.set_visible(False)
super(MplCanvas, self).__init__(figure)
self.setParent(parent)
self.ax = plt.axes([0,0,1,1], frameon=False)
self.ax.axis('off')
self.ax.get_xaxis().set_visible(False)
self.ax.get_yaxis().set_visible(False)
# i've played with the scale a little bit. at first it perfectly fits the window but it does not follow it as i scale horizontally
self.graph = EditableGraph([(0, 1)], scale=(2, 2), ax=self.ax)
plt.autoscale(axis='both', tight=True)
self.updateGeometry()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.canvas = MplCanvas(self)
self.lbl = QtWidgets.QLabel(self)
self.setCentralWidget(self.canvas)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main()
|
On my machine, your code now produces a tight fitting window. import sys
import matplotlib; matplotlib.use("Qt5Agg")
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
from netgraph import EditableGraph
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
figure = plt.figure()
figure.set_figheight(8)
figure.set_figwidth(6)
plt.tight_layout()
figure.patch.set_visible(False)
super(MplCanvas, self).__init__(figure)
self.setParent(parent)
self.ax = plt.axes([0,0,1,1], frameon=True)
self.ax.axis('off')
self.ax.get_xaxis().set_visible(False)
self.ax.get_yaxis().set_visible(False)
self.graph = EditableGraph([(0, 1)], scale=(1.5, 2), ax=self.ax)
plt.autoscale(axis='both', tight=True)
self.updateGeometry()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.canvas = MplCanvas(self)
self.lbl = QtWidgets.QLabel(self)
self.setCentralWidget(self.canvas)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main() |
Are you sure, you are not changing the figure dimensions in some other way, e.g. by maximizing the window? |
So, the behavior I want is to scale with the window. I am scaling it. |
Then you will probably need to write a custom class that handles class ResizableGraph(EditableGraph):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.origin = kwargs["origin"]
self.scale = kwargs["scale"]
self.figure_width = self.fig.width
self.figure_height = self.fig.height
self.fig.canvas.mpl_connect('resize_event', self._on_resize)
def _on_resize(event):
# TODO determine new figure dimensions
# TODO determine new scale
# TODO rescale node positions
# redraw
self._update_node_artists(self.nodes)
self._update_node_label_positions()
self._update_edges(self.edges)
self._update_edge_label_positions(edges)
self.fig.canvas.draw() |
I have gotten here so far. import sys
import matplotlib; matplotlib.use("Qt5Agg")
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
from netgraph import EditableGraph
class ResizableGraph(EditableGraph):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.origin = kwargs["origin"]
self.scale = kwargs["scale"]
# Can not use these width and height cause it throws
# an Attribute Error saying there is no such parameter in fig
# self.figure_width = self.fig.width
# self.figure_height = self.fig.height
self.fig.canvas.mpl_connect('resize_event', self._on_resize)
def _on_resize(self, event):
print(event)
height = event.height/self.fig.dpi
width = event.width / self.fig.dpi
# TODO determine new figure dimensions
self.fig.set_figheight(height)
self.fig.set_figwidth(width)
# TODO determine new scale
# Here is a calculus to define the proportion of the scale
if height > width:
self.scale = (1, height / width)
elif width > height:
self.scale = (width / height, 1)
else:
self.scale = (1, 1)
# TODO rescale node positions
# redraw
self._update_node_artists(self.nodes)
self._update_node_label_positions()
self._update_edges(self.edges)
self._update_edge_label_positions(self.edges)
self.fig.canvas.draw()
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
figure = plt.figure()
figure.set_figheight(6)
figure.set_figwidth(6)
plt.tight_layout()
figure.patch.set_visible(False)
super(MplCanvas, self).__init__(figure)
self.setParent(parent)
self.ax = plt.axes([0, 0, 1, 1], frameon=False)
self.ax.axis('off')
self.ax.get_xaxis().set_visible(False)
self.ax.get_yaxis().set_visible(False)
self.graph = ResizableGraph([(0, 1)], scale=(1, 1), ax=self.ax, origin=(0, 0))
plt.autoscale(axis='both', tight=True)
self.updateGeometry()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.canvas = MplCanvas(self)
self.lbl = QtWidgets.QLabel(self)
self.setCentralWidget(self.canvas)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main() I have tested the Also, when you say to rescale the node positions, I am not sure what do you mean. |
I have a newborn at home at the moment, so I am getting very little sleep. Take everything that I say with a grain of salt. That being said, I had imagined that on each resize, the node positions are stretched such that they fill the axis. import sys
import numpy as np
import matplotlib; matplotlib.use("Qt5Agg")
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
from netgraph import EditableGraph
class ResizableGraph(EditableGraph):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
kwargs.setdefault('origin', (0., 0.))
kwargs.setdefault('scale', (1., 1.))
self.origin = kwargs["origin"]
self.scale = kwargs["scale"]
self.figure_width = self.fig.bbox.width
self.figure_height = self.fig.bbox.height
self.fig.canvas.mpl_connect('resize_event', self._on_resize)
def _on_resize(self, event):
scale_x_by = self.fig.bbox.width / self.figure_width
scale_y_by = self.fig.bbox.height / self.figure_height
# rescale node positions
for node, (x, y) in self.node_positions.items():
new_x = ((x - self.origin[0]) * scale_x_by) + self.origin[0]
new_y = ((y - self.origin[1]) * scale_y_by) + self.origin[1]
self.node_positions[node] = np.array([new_x, new_y])
# update
self.figure_width = self.fig.bbox.width
self.figure_height = self.fig.bbox.height
self.scale = (scale_x_by * self.scale[0], scale_y_by * self.scale[1])
# redraw
self._update_node_artists(self.nodes)
self._update_node_label_positions()
self._update_edges(self.edges)
self._update_edge_label_positions(self.edges)
self.ax.autoscale_view()
self.fig.canvas.draw()
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
figure = plt.figure()
figure.set_figheight(8)
figure.set_figwidth(6)
# plt.tight_layout()
figure.patch.set_visible(False)
super(MplCanvas, self).__init__(figure)
self.setParent(parent)
self.ax = plt.axes([0, 0, 1, 1], frameon=True)
self.graph = ResizableGraph([(0, 1)], scale=(1.5, 2), ax=self.ax)
# self.ax.set_xlim(0, 1.5)
# self.ax.set_ylim(0, 2)
self.graph.ax.set_frame_on(True)
plt.autoscale(axis='both', tight=True)
self.updateGeometry()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.canvas = MplCanvas(self)
self.lbl = QtWidgets.QLabel(self)
self.setCentralWidget(self.canvas)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main() |
🎉 Congratulations on the newborn! First of all, it is a nice feature to rescale the node positions when resize the window, but it is not a requirement. However, I really think that is related to the main problem I imagine we are facing: The For exemple with a window size of 700x700: import sys
import matplotlib; matplotlib.use("Qt5Agg")
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
from netgraph import EditableGraph
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
figure = plt.figure()
super(MplCanvas, self).__init__(figure)
self.setParent(parent)
self.ax = plt.axes([0, 0, 1, 1], frameon=True)
self.graph = EditableGraph([(0, 1)], ax=self.ax)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.canvas = MplCanvas(self)
self.lbl = QtWidgets.QLabel(self)
self.setCentralWidget(self.canvas)
self.setFixedHeight(700)
self.setFixedWidth(700)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main() With the above code, I have a figure that fill the window (the graph can touch the edges): However, if I change the size of the window to something not squared, for example (600x500): import sys
import matplotlib; matplotlib.use("Qt5Agg")
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
from netgraph import EditableGraph
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
figure = plt.figure()
super(MplCanvas, self).__init__(figure)
self.setParent(parent)
self.ax = plt.axes([0, 0, 1, 1], frameon=True)
self.graph = EditableGraph([(0, 1)], ax=self.ax)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.canvas = MplCanvas(self)
self.lbl = QtWidgets.QLabel(self)
self.setCentralWidget(self.canvas)
self.setFixedHeight(600)
self.setFixedWidth(500)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main() The graph can not touch the edges anymore: However, if I apply the proportion calculus I have mentioned before: if height > width:
self.scale = (1, height / width)
elif width > height:
self.scale = (width / height, 1)
else:
self.scale = (1, 1) I get as a result a scale of (1, 1.2). And then if I changed it in the code: import sys
import matplotlib; matplotlib.use("Qt5Agg")
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
from netgraph import EditableGraph
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
figure = plt.figure()
super(MplCanvas, self).__init__(figure)
self.setParent(parent)
self.ax = plt.axes([0, 0, 1, 1], frameon=True)
self.graph = EditableGraph([(0, 1)], scale=(1, 1.2), ax=self.ax) #changed here
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.canvas = MplCanvas(self)
self.lbl = QtWidgets.QLabel(self)
self.setCentralWidget(self.canvas)
self.setFixedHeight(600)
self.setFixedWidth(500)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main() Then it matches the borders again: What makes me think that the scale is not being updated (and that is why the frame in your example remains the same even with the graph being stretched). It seems like the scale is set only once and remains the same, what would explain the bug behavior you have mentioned. Something else that got me wondering the about the problem of the scale not being updated is that in the code it does not have a "self" keyword attached to it. Wouldn't that make the scale declared on the new I am sorry for that quantity of code and images. Hope it brings us some clarification somehow. |
Hello @paulbrodersen ! Did you have a chance to read the last comment I left here? Did it help somehow?
|
Hi @LBeghini, I haven't looked into this issue further. I will take another stab at the problem early next week (I am unavailable most of today). |
My last proposed solution was actually quite close. I just had to manually force an update of the axis limits as As long as the matplotlib figure has the same (initial) aspect ratio (via import sys
import numpy as np
import matplotlib; matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from netgraph import EditableGraph
class ResizableGraph(EditableGraph):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
kwargs.setdefault('origin', (0., 0.))
kwargs.setdefault('scale', (1., 1.))
self.origin = kwargs["origin"]
self.scale = kwargs["scale"]
self.figure_width = self.fig.bbox.width
self.figure_height = self.fig.bbox.height
self.fig.canvas.mpl_connect('resize_event', self._on_resize)
def _on_resize(self, event, pad=0.05):
# determine ratio new : old
scale_x_by = self.fig.bbox.width / self.figure_width
scale_y_by = self.fig.bbox.height / self.figure_height
self.figure_width = self.fig.bbox.width
self.figure_height = self.fig.bbox.height
# rescale node positions
for node, (x, y) in self.node_positions.items():
new_x = ((x - self.origin[0]) * scale_x_by) + self.origin[0]
new_y = ((y - self.origin[1]) * scale_y_by) + self.origin[1]
self.node_positions[node] = np.array([new_x, new_y])
# update axis dimensions
self.scale = (scale_x_by * self.scale[0],
scale_y_by * self.scale[1])
xmin = self.origin[0] - pad * self.scale[0]
ymin = self.origin[1] - pad * self.scale[1]
xmax = self.origin[0] + self.scale[0] + pad * self.scale[0]
ymax = self.origin[1] + self.scale[1] + pad * self.scale[1]
self.ax.axis([xmin, xmax, ymin, ymax])
# redraw
self._update_node_artists(self.nodes)
self._update_node_label_positions()
self._update_edges(self.edges)
self._update_edge_label_positions(self.edges)
self.fig.canvas.draw()
class MplCanvas(FigureCanvas):
def __init__(self, parent=None):
self.fig, self.ax = plt.subplots(figsize=(16, 8))
self.ax.set_position([0, 0, 1, 1])
self.graph = ResizableGraph([(0, 1)], scale=(2, 1), ax=self.ax)
self.graph.ax.set_frame_on(True)
super(MplCanvas, self).__init__(self.fig)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.canvas = MplCanvas(self)
self.setCentralWidget(self.canvas)
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main() However, as you have noted correctly, the scale argument is not an attribute of the netgraph |
Hi @LBeghini, I will close the issue for now as I haven't heard from you for a while, and the solution above seems to work (at least in my hands). Feel free to re-open if necessary. |
That's all right! Yesterday we just merged this proposed changes to our project! |
Hello!
I am trying to embed Netgraph's plots inside my PyQt application, however I am not being able to make the figure occupy the entire window nor scale with it. The graphs keep touching only one of the borders of the figure, and I can't make the figure in only one direction (x or y).
It's been a while since I started to try to make this right, looking into MatPlotLib's documentations. I have even written a few questions at StackOverflow about it, however without any success.(Scalable MatPlotLib Figure with PyQt window and FigureCanvas not entirely filing PyQt Window)
The behavior I face can be seen in the image:
It is possible to see that the graph can't touch the horizontal borders of the Window, doesn't matter how I scale it. I also keep receiving this Warnings whenever I touch the blank space between the border of the Figure and the border of the Window.
What I want is for the graph to always touch the border of the window.
Could you help me somehow? At this point I don't know if this could be something related to the Netgraph library itself.
Here is the code I am using for testing:
The text was updated successfully, but these errors were encountered: