So, my code is a mess, but I hope it will help you get started and get the basic ideas.
The first idea is that you need to bind the <Motion>
mouse event to the c
canvas.
The bind
method takes two arguments: an event, which says when to do something, and a function, which says what to do.
I chose to define a redraw_chart
function, that draws the pie according to the position of the mouse.
This function is what will be called on a <Motion>
event, so I bind as follows:
c.bind('<Motion>', lambda e: redraw_chart(e.x, e.y))
The lambda
function is just an anonymous function, that receives the event raised, and passes the two coordinates of the event (that is, the coordinates of the mouse) to the redraw_chart
.
The redraw_chart
function is really dumb: it draws the pie according to the coordinates it received:
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")
Now, what are redCode
, blueCode
and whiteCode
?
They are the addresses of the three arc objects created by the c.create_arc
method.
They are useful to modify the arcs, so as to avoid creating new ones.
There is still one thing left to define: the get_arc
function.
The get_arc
function takes a (x, y)
couple, representing a point of the canvas, and returns the corresponding arc:
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
It relies on the is_in_arc
function, that takes a point, a portion of the pie, and tells if the point lies in the portion.
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
The np.arctan2
function from numpy
returns the angle in radians corresponding to the (x, y)
point.
Then, the fract
method returns the corresponding value in degrees.
I modified it, because I did not really understand yours:
def frac(n):
if n < 0:
n += 2*np.pi
return 360 * n / (2*np.pi)
So here is what it looks like. You cannot see the cursor one the screenshot, but I guarantee you that the parts turn green when hovered.

Here is the complete code:
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()