anntzer / mplcursors

Interactive data selection cursors for Matplotlib.
https://mplcursors.readthedocs.io
zlib License
113 stars 19 forks source link

Don't override alignments set by the user in a callback #24

Open luizkeng opened 4 years ago

luizkeng commented 4 years ago

Hello @anntzer ,

Is there any special care when we use mplcursors in a PyQt embedded matplotlib graph? I observed two issues in this context:

Do you have any idea of how to fix these issues?

Anyway, thanks for your attention.

luizkeng commented 4 years ago

Hi @anntzer !

I've got how to fix the first issue. @jorgesmok had a similar problem and suggested to set the canvas Focus Policy to Qt.ClickFocus. Finally the bindings worked fine!! I would edit my last post, but I decided to keep the first issue and the solution in the case of anyone need in the future. Anyway, the second one strangely persists.

anntzer commented 4 years ago

I have written a fair ampunt of Qt GUIs myself, so things should work fine there. What was your focus? Matplotlib sets the default to Qt.StrongFocus which should include ClickFocus.

Do you have an example to repro the second case? e.g. the code and tell me where to click. Otherwise it's always tricky to investigate.

luizkeng commented 4 years ago

The focus was on the widget, but if I didn't include set the canvas Focus Policy to Qt.ClickFocus (or even to Qt.StrongFocus) nothing works.

I'll try to build a example to mimetize the my own problem and put it here soon.

luizkeng commented 4 years ago

In the following code I think you can see what I talked about alignment. Between the x points 1 and 2 the "Sample" annotation stays on the left, however for 14 - 15 x points it appears at right.

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QSizePolicy, QMessageBox, QWidget, QPushButton
from PyQt5.QtGui import QIcon
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import mplcursors
import random

class App(QMainWindow):

    def __init__(self):
        super().__init__()
        self.left = 10
        self.top = 10
        self.title = 'PyQt5 matplotlib example - pythonspot.com'
        self.width = 640
        self.height = 400
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        m = PlotCanvas(self, width=5, height=4)
        m.move(0,0)
        self.show()

class PlotCanvas(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)

        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                QSizePolicy.Expanding,
                QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)
        self.plot()

    def plot(self):
        data = [random.random() for i in range(25)]
        ax = self.figure.add_subplot(111)
        ax.plot(data, 'r-')
        ax.set_title('PyQt Matplotlib Example')
        self.draw()

        mpl_cursor = mplcursors.cursor(ax, hover = 2)        

        @mpl_cursor.connect("add")
        def on_add(sel):
            sel.annotation.set(text = f"Sample: {sel.artist.get_label()}\nX: {sel.target[0]} \nY: {sel.target[1]}", ha = "left", va="center")
            sel.annotation.set_ha('left')
            sel.annotation.set_weight('bold')            

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())
anntzer commented 4 years ago

Ah yes. This is more or less intentional: I need to keep the annotation within the figure (I can't draw outside of the figure and cropping the annotation would be pretty bad), so I detect whether the annotation on the left or on the right of the selected point; then based on that info I pick the alignment (left or right). You can force the position (as in the bar.py example) in which case I'll also leave whatever alignment you set (if any), but if you don't then alignment will be autopicked for you, which I guess I could also avoid if you explicitly set an alignment? How does that look to you? Also I guess this behavior could be better documented...

luizkeng commented 4 years ago

Oh yeah. I got it. For me it's ok.

I have only one more question: how can a set the alignment of the text inside the annotation? I observed, in the aforementioned example, that when annotation is left the text goes right. Also, when annotation is right, the text goes left. I'm attaching two example to make it more clear.

left_text right_text
anntzer commented 4 years ago

Looking at the docs again, I think the behavior is alluded to in the Note under https://mplcursors.readthedocs.io/en/latest/#customization. It's not really explicit; OTOH I'm not sure I really want to emphasize it too much as it's a bit obscure.

I think keeping user-set alignments is reasonable, but a quick try suggests it will be slightly tricky to achieve. Please consider submitting a PR; otherwise I'll try to work on a fix myself but can't make any schedule guarantees.