
# hyperbolaString.py
# Andrew Davison, ad@coe.psu.ac.th, Oct. 2025
'''
Animate a point around the upper branch of
a hyperbola and draw lines
from the point to the foci. Print the lengths of
the lines, and their difference, on the graph. 
The difference 
should be 2*semiMajor (6 in this case).

The ellipse is drawn using a polar equation.

Very similar to ellipseString.py
'''

import math
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


def hyperbola(theta):
  x = semiMinor * math.sinh(theta)
  y = semiMajor * math.cosh(theta)
  return (x,y)


def initElems():
  # Initialize the animation elements
  dot.set_data([], [])   # the moving dot
  line1.set_data([], [])  # lines to the foci
  line2.set_data([], [])
  text1.set_text('')  # foci lengths
  text2.set_text('')
  textDiff.set_text('')  # difference of lengths
  return dot, line1, line2, text1, text2, textDiff


def animate(i):
  # Update the animation for frame i
  theta = (i - 20)/10 
   # Parameter for a vertical hyperbola branch

  # Position of the dot on the hyperbola
  xDot, yDot = hyperbola(theta)
  dot.set_data([xDot], [yDot])

  # Line from dot to focal point 1
  x1Line = [xDot, foci1[0]]
  y1Line = [yDot, foci1[1]]
  line1.set_data(x1Line, y1Line)

  # Line from dot to focal point 2
  x2Line = [xDot, foci2[0]]
  y2Line = [yDot, foci2[1]]
  line2.set_data(x2Line, y2Line)

  # Calculate lengths and display them
  len1 = math.sqrt((xDot - foci1[0])**2 + \
                   (yDot - foci1[1])**2)
  len2 = math.sqrt((xDot - foci2[0])**2 + \
                    (yDot - foci2[1])**2)
  diffLen = abs(len1 - len2)

  # Positions for the text labels
  pos1 = (xDot + foci1[0]) / 2, (yDot + foci1[1]) / 2
  pos2 = (xDot + foci2[0]) / 2, (yDot + foci2[1]) / 2
  posDiff = (xDot-1, yDot-1.5)

  text1.set_position(pos1)
  text1.set_text(f'{len1:.2f}')
  text2.set_position(pos2)
  text2.set_text(f'{len2:.2f}')

  # Ualso show the difference of the lengths
  textDiff.set_position(posDiff)
  textDiff.set_text(f'Diff: {diffLen:.2f}')

  return dot, line1, line2, text1, text2, textDiff


# vertical hyperbola parameters
semiMajor = 3  # axes lengths
semiMinor = 2
focalDist = math.sqrt(semiMajor**2 + semiMinor**2)
foci1 = (0, -focalDist)
foci2 = (0, focalDist)

# the figure and axes
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_xlim(-(semiMinor + 10), semiMinor + 10)
ax.set_ylim(-(semiMajor + 10), semiMajor + 10)
ax.set_aspect('equal', adjustable='box')
ax.grid(True)
ax.set_title("Animation of a Dot on a Hyperbola with Focal Lines")

# Plot the hyperbola
thetas = [i/10 for i in range(-20, 21)]
coords = [hyperbola(theta) for theta in thetas]
ax.plot(*zip(*coords), 'b-', label='Hyperbola Branch')

# Plot the focal points
ax.plot(foci1[0], foci1[1], 'ro', markersize=8, 
                            label='Focal Pts')
ax.plot(foci2[0], foci2[1], 'ro', markersize=8)

# Initialize the animated elements
dot, = ax.plot([], [], 'go', markersize=10, 
                         label='Animated Dot')
line1, = ax.plot([], [], 'r--')
line2, = ax.plot([], [], 'r--')
text1 = ax.text(0, 0, '', fontsize=12)
text2 = ax.text(0, 0, '', fontsize=12)
textDiff = ax.text(0, 0, '', fontsize=12)

# start the animation
anim = FuncAnimation(fig, animate, 
                    frames=41, interval=100, 
                    blit=True, init_func=initElems)
plt.show()