将鼠标悬停在饼图上时更改饼图的颜色

Posted

技术标签:

【中文标题】将鼠标悬停在饼图上时更改饼图的颜色【英文标题】:change color of a pie chart when hovering mouse over it 【发布时间】:2017-09-01 12:07:21 【问题描述】:

我刚刚用 Python 用 Tkinter 创建了一个饼图,如下所示:

def frac(n): 
    return 360. * n / 500

import Tkinter
c = Tkinter.Canvas(width=100, height=100); c.pack()
c.create_arc((2,2,98,98), fill="red", start=frac(0), extent = 
frac(100))
c.create_arc((2,2,98,98), fill="blue", start=frac(100), extent = frac(400))
c.create_arc((2,2,98,98), fill="white", start=frac(400), extent = frac(100), width=0)
c.mainloop()

这是结果:

现在,我想在将鼠标悬停在每个切片上时更改其颜色。我怎样才能做到这一点?非常感谢

【问题讨论】:

在 matplotlib 的帮助下尝试一下怎么样!!!! @Mandy8055 matplotlib 可以处理悬停这样的事情吗? 是的,当然!!!你所需要的只是事件处理:matplotlib.org/users/event_handling.html.Although我几乎没有信息表明这可以完全在 Tkinter 中实现;它可以在 matplotlib 中实现 【参考方案1】:

所以,我的代码是一团糟,但我希望它能帮助你入门并获得基本的想法。

第一个想法是需要将<Motion>鼠标事件绑定到c画布上。 bind 方法有两个参数:一个事件,它说明什么时候做某事,一个函数,它说明做什么。 我选择定义一个redraw_chart 函数,根据鼠标的位置绘制饼图。 这个函数是在<Motion>事件上调用的,所以我绑定如下:

c.bind('<Motion>', lambda e: redraw_chart(e.x, e.y))

lambda函数只是一个匿名函数,它接收引发的事件,并将事件的两个坐标(即鼠标坐标)传递给redraw_chart

redraw_chart 函数真的很笨:它根据收到的坐标绘制饼图:

def redraw_chart(x, y):
    global redCode, blueCode, whiteCode

    arc = get_arc(x, y)

    if arc == "red":
        c.itemconfig(redCode, fill="green")
        c.itemconfig(redCode, fill="blue")
        c.itemconfig(redCode, fill="white")   

    elif arc == "blue":
        c.itemconfig(redCode, fill="red")
        c.itemconfig(redCode, fill="green")
        c.itemconfig(redCode, fill="white")   

    elif arc == "white":
        c.itemconfig(redCode, fill="red")
        c.itemconfig(redCode, fill="blue")
        c.itemconfig(redCode, fill="green")   

    else:
        c.itemconfig(redCode, fill="green")
        c.itemconfig(redCode, fill="blue")
        c.itemconfig(redCode, fill="white")   

现在,redCodeblueCodewhiteCode 是什么? 它们是c.create_arc 方法创建的三个弧对象的地址。 它们对于修改弧线很有用,以避免创建新的弧线。 还有一件事需要定义:get_arc 函数。

get_arc 函数接受(x, y) 对,代表画布的一个点,并返回相应的弧:

def get_arc(x, y):
    if is_in_arc(x, y, redArc[0], redArc[0]+redArc[1]):
        return "red"
    elif is_in_arc(x, y, blueArc[0], blueArc[0]+blueArc[1]):
        return "blue"
    elif is_in_arc(x, y, whiteArc[0], whiteArc[0]+whiteArc[1]):
        return "white"
    else:
        return None

它依赖于is_in_arc 函数,该函数获取一个点、饼图的一部分,并判断该点是否位于该部分中。

def is_in_arc(x, y, angle0, angle1):
    if (x-50)**2 + (y-50)**2 > 48**2:
        return False

    theta = - np.arctan2(y-50, x-50)
    return angle0 <= frac(theta) <= angle1

来自numpynp.arctan2 函数返回与(x, y) 点对应的弧度角。 然后,fract 方法返回相应的度值。 我修改了它,因为我并没有真正理解你的:

def frac(n):
    if n < 0:
        n += 2*np.pi
    return 360 * n / (2*np.pi)

这就是它的样子。您无法在屏幕截图中看到光标,但我向您保证,悬停时部分会变为绿色。

完整代码如下:

import tkinter as tk
import numpy as np


def frac(n):
    if n < 0:
        n += 2*np.pi
    return 360 * n / (2*np.pi)


c = tk.Canvas(width=100, height=100)
c.pack()

redArc = (frac(0), frac(np.pi/3))
blueArc = (frac(np.pi/3), frac(4*np.pi/3))
whiteArc = (frac(5*np.pi/3), frac(np.pi/3))

redCode = c.create_arc((2,2,98,98), fill="red", start=redArc[0], extent=redArc[1])
blueCode = c.create_arc((2,2,98,98), fill="blue", start=blueArc[0], extent=blueArc[1])
whiteCode = c.create_arc((2,2,98,98), fill="white", start=whiteArc[0], extent=whiteArc[1])


def is_in_arc(x, y, angle0, angle1):
    if (x-50)**2 + (y-50)**2 > 48**2:
        return False
    theta = - np.arctan2(y-50, x-50)
    return angle0 <= frac(theta) <= angle1


def get_arc(x, y):
    if is_in_arc(x, y, redArc[0], redArc[0]+redArc[1]):
        return "red"
    elif is_in_arc(x, y, blueArc[0], blueArc[0]+blueArc[1]):
        return "blue"
    elif is_in_arc(x, y, whiteArc[0], whiteArc[0]+whiteArc[1]):
        return "white"
    else:
        return None


def redraw_chart(x, y):
    global redCode, blueCode, whiteCode

    arc = get_arc(x, y)

    if arc == "red":
        c.itemconfig(redCode, fill="green")
        c.itemconfig(redCode, fill="blue")
        c.itemconfig(redCode, fill="white")   

    elif arc == "blue":
        c.itemconfig(redCode, fill="red")
        c.itemconfig(redCode, fill="green")
        c.itemconfig(redCode, fill="white")   

    elif arc == "white":
        c.itemconfig(redCode, fill="red")
        c.itemconfig(redCode, fill="blue")
        c.itemconfig(redCode, fill="green")   

    else:
        c.itemconfig(redCode, fill="green")
        c.itemconfig(redCode, fill="blue")
        c.itemconfig(redCode, fill="white") 


c.bind('<Motion>', lambda e: redraw_chart(e.x, e.y))

c.mainloop()

【讨论】:

这段代码似乎只是在现有的弧上堆积了新的弧,最终会耗尽内存。重新着色现有的弧(通过c.itemconfig() 将是一个更好的方法。 @jasonharper 哦,对了,我忘记了这个方法。但是物品还是被删除了,所以虽然不太好,但物品并没有真正堆积起来。 您没有删除这些项目。您正在删除项目的 ID。 @jasonharper 看来我把delcanvas.delete() 混在一起了……我的错。但无论如何,canvas.itemconfig 更好。顺便说一句,我知道这段代码很难看,但我尝试从 OP 的代码开始。【参考方案2】:

您可以使用bind 方法来绑定事件并重新绘制图表,如下所示:

def on_enter(event):
    c.create_arc((2,2,98,98), fill="orange", start=frac(100), extent = frac(400))
(...)
c.bind('<Enter>', on_enter)

请参阅this answer 了解如何将整个内容嵌入到类中的示例。

【讨论】:

你怎么知道悬停的是什么弧? 事件有光标的 x:y 坐标。基于此和圆心,您可以计算出光标下的弧线。我的数学课太远了,我无法拿出公式,但我觉得它应该很容易找到。

以上是关于将鼠标悬停在饼图上时更改饼图的颜色的主要内容,如果未能解决你的问题,请参考以下文章

将鼠标悬停在图像上时如何更改 SVG 的颜色?

Chart.js - 鼠标悬停时自定义css到数据集

将鼠标悬停在列表上时如何更改锚点颜色

将鼠标悬停在 li 上时如何更改所有字体颜色

Eclipse,将鼠标悬停在关键字上时更改弹出文本背景颜色

将鼠标悬停在上下文菜单上时如何更改 li 项的颜色