【问题标题】:Annotating a Matplotlib Polycollection bar timeline注释 Matplotlib Polycollection 条形时间线
【发布时间】:2021-02-18 05:12:38
【问题描述】:

我的问题是:当使用“motion_notify_event”将鼠标悬停在条上时如何注释 Matplotlib Polycollection 条形图?我花了一天的时间试图完成这项工作,但我无法理解一个多集合对象以及如何索引它。希望有人能帮助解决这个问题,我花了这么多时间,现在不想放弃!

在下面的链接中可以找到使用“motion_notify_event”进行分散、线条和条形注释的绝佳解决方案: Possible to make labels appear when hovering over a point in matplotlib?

但是,上述解决方案不处理“matplotlib.collections.polycollection”对象。我正在努力寻找使用上述解决方案中的代码访问该对象中保存的顶点和数组的方法。是否可以索引多集合;使用索引访问原始数据中的字符串值;然后在悬停时注释位置?

我使用了来自以下链接的 Polycollection 条时间线的修改示例,我已将其与上述链接中的悬停/注释示例融合在一起。 How to individually label bars in Matplotlib plot?

    import datetime as dt
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    from matplotlib.collections import PolyCollection

    data = [(dt.datetime(2018, 7, 17, 0, 15), dt.datetime(2018, 7, 17, 0, 30), 'sleep','nap_1'),
           (dt.datetime(2018, 7, 17, 0, 30), dt.datetime(2018, 7, 17, 0, 45), 'eat', 'snack_1'),
           (dt.datetime(2018, 7, 17, 0, 45), dt.datetime(2018, 7, 17, 1, 0), 'work', 'meeting_1'),
           (dt.datetime(2018, 7, 17, 1, 0), dt.datetime(2018, 7, 17, 1, 30), 'sleep','nap_2'),
           (dt.datetime(2018, 7, 17, 1, 15), dt.datetime(2018, 7, 17, 1, 30), 'eat', 'snack_2'), 
           (dt.datetime(2018, 7, 17, 1, 30), dt.datetime(2018, 7, 17, 1, 45), 'work', 'project_2')]

    cats = {"sleep" : 1, "eat" : 2, "work" : 3}
    colormapping = {"sleep" : "C0", "eat" : "C1", "work" : "C2"}

    verts = []
    colors = []
    for d in data:
        v =  [(mdates.date2num(d[0]), cats[d[2]]-.4),
              (mdates.date2num(d[0]), cats[d[2]]+.4),
              (mdates.date2num(d[1]), cats[d[2]]+.4),
              (mdates.date2num(d[1]), cats[d[2]]-.4),
              (mdates.date2num(d[0]), cats[d[2]]-.4)]
        verts.append(v)
        colors.append(colormapping[d[2]])

    bars = PolyCollection(verts, facecolors=colors)

    fig, ax = plt.subplots()
    ax.add_collection(bars)
    ax.autoscale()
    loc = mdates.MinuteLocator(byminute=[0,15,30,45])
    ax.xaxis.set_major_locator(loc)
    ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))

    ax.set_yticks([1,2,3])
    ax.set_yticklabels(["sleep", "eat", "work"])

    annot = ax.annotate("", xy=(0, 0), xytext=(20, 20), textcoords="offset points",
      bbox=dict(boxstyle="round", fc="w"),
      arrowprops=dict(arrowstyle="->"))
    annot.set_visible(False)


    def update_annot(ind):
        # how to index the polycollection? get_offsets causes an error
        pos = bars.get_offsets()[ind["ind"][0]] 
        # causes 'IndexError: index 5 is out of bounds for axis 0 with size 1'
        annot.xy = pos
        # If we can get an index, can we use it to access the original 'data' to get the 
        # string value at d[3] from data ('nap_1') and use it to annotate?
        text = ""

        annot.set_text(text)
        annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
        annot.get_bbox_patch().set_alpha(0.4)


    def hover(event):
        vis = annot.get_visible()
        if event.inaxes == ax:
            cont, ind = bars.contains(event)
            if cont:
                update_annot(ind)
                annot.set_visible(True)
                fig.canvas.draw_idle()
            else:
                if vis:
                    annot.set_visible(False)
                    fig.canvas.draw_idle()

    fig.canvas.mpl_connect("motion_notify_event", hover)

    plt.show()

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    获取想要的数据的方法有点复杂,可以通过调试器挖掘出来:

    • bars.get_paths() 给出了Path 对象(多边形)的列表。
    • 可以将其编入索引以访问单个柱
    • bars.get_paths()[ind].get_extents() 给出了 bar 的边界框
    • bars.get_paths()[ind].get_extents().get_points() 给出边界框的左下角和右上角。
    import datetime as dt
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    from matplotlib.collections import PolyCollection
    
    data = [(dt.datetime(2018, 7, 17, 0, 15), dt.datetime(2018, 7, 17, 0, 30), 'sleep', 'nap_1'),
            (dt.datetime(2018, 7, 17, 0, 30), dt.datetime(2018, 7, 17, 0, 45), 'eat', 'snack_1'),
            (dt.datetime(2018, 7, 17, 0, 45), dt.datetime(2018, 7, 17, 1, 0), 'work', 'meeting_1'),
            (dt.datetime(2018, 7, 17, 1, 0), dt.datetime(2018, 7, 17, 1, 30), 'sleep', 'nap_2'),
            (dt.datetime(2018, 7, 17, 1, 15), dt.datetime(2018, 7, 17, 1, 30), 'eat', 'snack_2'),
            (dt.datetime(2018, 7, 17, 1, 30), dt.datetime(2018, 7, 17, 1, 45), 'work', 'project_2')]
    
    cats = {"sleep": 1, "eat": 2, "work": 3}
    colormapping = {"sleep": "C0", "eat": "C1", "work": "C2"}
    
    verts = []
    colors = []
    for d in data:
        v = [(mdates.date2num(d[0]), cats[d[2]] - .4),
             (mdates.date2num(d[0]), cats[d[2]] + .4),
             (mdates.date2num(d[1]), cats[d[2]] + .4),
             (mdates.date2num(d[1]), cats[d[2]] - .4),
             (mdates.date2num(d[0]), cats[d[2]] - .4)]
        verts.append(v)
        colors.append(colormapping[d[2]])
    
    bars = PolyCollection(verts, facecolors=colors)
    
    fig, ax = plt.subplots()
    ax.add_collection(bars)
    ax.autoscale()
    loc = mdates.MinuteLocator(byminute=[0, 15, 30, 45])
    ax.xaxis.set_major_locator(loc)
    ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))
    
    ax.set_yticks([1, 2, 3])
    ax.set_yticklabels(["sleep", "eat", "work"])
    
    annot = ax.annotate("", xy=(0, 0), xytext=(20, 20), textcoords="offset points",
                        bbox=dict(boxstyle="round", fc="w"),
                        arrowprops=dict(arrowstyle="->"))
    annot.set_visible(False)
    
    def update_annot(ind):
        pos = bars.get_paths()[ind].get_extents().get_points()[1]
        annot.xy = pos
        text = f"{data[ind][2]}: {data[ind][3]}"
        annot.set_text(text)
        # annot.get_bbox_patch().set_facecolor(colormapping[data[ind][2]])
        annot.get_bbox_patch().set_facecolor(bars.get_facecolors()[ind])
        annot.get_bbox_patch().set_alpha(0.4)
    
    def hover(event):
        vis = annot.get_visible()
        if event.inaxes == ax:
            cont, ind = bars.contains(event)
            if cont:
                update_annot(ind["ind"][0])
                annot.set_visible(True)
                fig.canvas.draw_idle()
            else:
                if vis:
                    annot.set_visible(False)
                    fig.canvas.draw_idle()
    
    fig.canvas.mpl_connect("motion_notify_event", hover)
    plt.show()
    

    【讨论】:

    • 好一个约翰!很好的解释!
    猜你喜欢
    • 2020-07-16
    • 2017-10-18
    • 2018-08-02
    • 2016-10-13
    • 2019-01-01
    • 1970-01-01
    • 2022-01-16
    • 1970-01-01
    • 2023-02-10
    相关资源
    最近更新 更多