@@ -760,6 +760,64 @@ def G(self):
760760
761761 return G
762762
763+ def _hover_info (self , frequency = None ):
764+ """Generate hover information for bearing element.
765+
766+ This method can be overridden by subclasses to customize the hover
767+ information displayed when hovering over the bearing element in plots.
768+
769+ Parameters
770+ ----------
771+ frequency : float, optional
772+ Frequency at which to display coefficients (rad/s).
773+ Not used - displays coefficients at first and last frequencies.
774+
775+ Returns
776+ -------
777+ customdata : list
778+ Data to attach to hover trace.
779+ hovertemplate : str
780+ Template string for hover display with HTML formatting.
781+
782+ Examples
783+ --------
784+ >>> bearing = bearing_example()
785+ >>> customdata, hovertemplate = bearing._hover_info()
786+ >>> customdata[0] # node number
787+ 0
788+ """
789+ # Get first and last frequencies
790+ if self .frequency is not None :
791+ if hasattr (self .frequency , "__iter__" ):
792+ freq_0 = self .frequency [0 ]
793+ freq_1 = self .frequency [- 1 ]
794+ else :
795+ freq_0 = freq_1 = self .frequency
796+ else :
797+ freq_0 = freq_1 = 0
798+
799+ # Convert frequencies to RPM
800+ freq_0_rpm = Q_ (freq_0 , "rad/s" ).to ("RPM" ).m
801+ freq_1_rpm = Q_ (freq_1 , "rad/s" ).to ("RPM" ).m
802+
803+ # Build hover template directly without intermediate list
804+ hovertemplate = f"Bearing at Node: { self .n } <br>"
805+ if self .tag is not None :
806+ hovertemplate = f"Tag: { self .tag } <br>" + hovertemplate
807+
808+ hovertemplate += f"Frequency: { freq_0_rpm :.2f} ... { freq_1_rpm :.2f} RPM<br>"
809+ hovertemplate += (
810+ f"Kxx: { self .kxx_interpolated (freq_0 ):.3e} ... { self .kxx_interpolated (freq_1 ):.3e} N/m<br>"
811+ f"Kyy: { self .kyy_interpolated (freq_0 ):.3e} ... { self .kyy_interpolated (freq_1 ):.3e} N/m<br>"
812+ f"Cxx: { self .cxx_interpolated (freq_0 ):.3e} ... { self .cxx_interpolated (freq_1 ):.3e} N·s/m<br>"
813+ f"Cyy: { self .cyy_interpolated (freq_0 ):.3e} ... { self .cyy_interpolated (freq_1 ):.3e} N·s/m<br>"
814+ )
815+
816+ # customdata is still needed for plotly, but can be minimal
817+ customdata = [self .n ]
818+
819+ return customdata , hovertemplate
820+
763821 def _patch (self , position , fig ):
764822 """Bearing element patch.
765823
@@ -806,6 +864,29 @@ def _patch(self, position, fig):
806864 fig .add_trace (go .Scatter (x = x_bot , y = np .add (yl_bot , yc_pos ), ** default_values ))
807865 fig .add_trace (go .Scatter (x = x_bot , y = np .add (yu_bot , yc_pos ), ** default_values ))
808866
867+ # Add hover information marker at the center of bottom base
868+ customdata , hovertemplate = self ._hover_info ()
869+ # Scale marker size proportionally to the bearing icon height
870+ # icon_h already includes the scale_factor effect from rotor assembly
871+ marker_size = icon_h * 200 # proportional to actual bearing size
872+ hover_marker_values_top = dict (
873+ mode = "markers" ,
874+ x = [zpos ],
875+ y = [ypos + icon_h / 2 ],
876+ marker = dict (size = marker_size , color = self .color , opacity = 0 ),
877+ customdata = [customdata ],
878+ hovertemplate = hovertemplate ,
879+ hoverinfo = "text" ,
880+ name = self .tag ,
881+ legendgroup = "bearings" ,
882+ showlegend = False ,
883+ )
884+ fig .add_trace (go .Scatter (** hover_marker_values_top ))
885+ # copy the customdata and hovertemplate from the top marker just multiplying the y value by -1
886+ hover_marker_values_bottom = hover_marker_values_top .copy ()
887+ hover_marker_values_bottom ["y" ] = [- 1 * hover_marker_values_top ["y" ][0 ]]
888+ fig .add_trace (go .Scatter (** hover_marker_values_bottom ))
889+
809890 # plot top base
810891 x_top = [zpos , zpos , zs0 , zs1 ]
811892 yl_top = [
@@ -1363,6 +1444,63 @@ def __init__(
13631444 # make seals with half the bearing size as a default
13641445 self .scale_factor = scale_factor if scale_factor else self .scale_factor / 2
13651446
1447+ def _hover_info (self , frequency = None ):
1448+ """Generate hover information for seal element.
1449+
1450+ Overrides the base class method to include seal-specific information
1451+ such as cross-coupled coefficients and seal leakage.
1452+
1453+ Parameters
1454+ ----------
1455+ frequency : float, optional
1456+ Frequency at which to display coefficients (rad/s).
1457+ Not used - displays coefficients at first and last frequencies.
1458+
1459+ Returns
1460+ -------
1461+ customdata : list
1462+ Data to attach to hover trace.
1463+ hovertemplate : str
1464+ Template string for hover display with HTML formatting.
1465+ """
1466+ # Get first and last frequencies
1467+ if self .frequency is not None :
1468+ if hasattr (self .frequency , "__iter__" ):
1469+ freq_0 = self .frequency [0 ]
1470+ freq_1 = self .frequency [- 1 ]
1471+ else :
1472+ freq_0 = freq_1 = self .frequency
1473+ else :
1474+ freq_0 = freq_1 = 0
1475+
1476+ # Convert frequencies to RPM
1477+ freq_0_rpm = Q_ (freq_0 , "rad/s" ).to ("RPM" ).m
1478+ freq_1_rpm = Q_ (freq_1 , "rad/s" ).to ("RPM" ).m
1479+
1480+ # Build hover template directly
1481+ hovertemplate = f"Seal at Node: { self .n } <br>"
1482+ if self .tag is not None :
1483+ hovertemplate = f"Tag: { self .tag } <br>" + hovertemplate
1484+
1485+ hovertemplate += f"Frequency: { freq_0_rpm :.2f} ... { freq_1_rpm :.2f} RPM<br>"
1486+ hovertemplate += (
1487+ f"Kxx: { self .kxx_interpolated (freq_0 ):.3e} ... { self .kxx_interpolated (freq_1 ):.3e} N/m<br>"
1488+ f"Kyy: { self .kyy_interpolated (freq_0 ):.3e} ... { self .kyy_interpolated (freq_1 ):.3e} N/m<br>"
1489+ f"Kxy: { self .kxy_interpolated (freq_0 ):.3e} ... { self .kxy_interpolated (freq_1 ):.3e} N/m<br>"
1490+ f"Kyx: { self .kyx_interpolated (freq_0 ):.3e} ... { self .kyx_interpolated (freq_1 ):.3e} N/m<br>"
1491+ f"Cxx: { self .cxx_interpolated (freq_0 ):.3e} ... { self .cxx_interpolated (freq_1 ):.3e} N·s/m<br>"
1492+ f"Cyy: { self .cyy_interpolated (freq_0 ):.3e} ... { self .cyy_interpolated (freq_1 ):.3e} N·s/m<br>"
1493+ f"Cxy: { self .cxy_interpolated (freq_0 ):.3e} ... { self .cxy_interpolated (freq_1 ):.3e} N·s/m<br>"
1494+ f"Cyx: { self .cyx_interpolated (freq_0 ):.3e} ... { self .cyx_interpolated (freq_1 ):.3e} N·s/m<br>"
1495+ )
1496+
1497+ if self .seal_leakage is not None :
1498+ hovertemplate += f"Seal Leakage: { self .seal_leakage :.3e} <br>"
1499+
1500+ customdata = [self .n ]
1501+
1502+ return customdata , hovertemplate
1503+
13661504
13671505class BallBearingElement (BearingElement ):
13681506 """A bearing element for ball bearings.
@@ -1490,6 +1628,44 @@ def __init__(
14901628 color = color ,
14911629 )
14921630
1631+ def _hover_info (self , frequency = None ):
1632+ """Generate hover information for ball bearing element.
1633+
1634+ Overrides the base class method to include ball bearing-specific
1635+ geometric and operating parameters.
1636+
1637+ Parameters
1638+ ----------
1639+ frequency : float, optional
1640+ Frequency at which to display coefficients (rad/s).
1641+ Not used for ball bearings (frequency-independent).
1642+
1643+ Returns
1644+ -------
1645+ customdata : list
1646+ Data to attach to hover trace.
1647+ hovertemplate : str
1648+ Template string for hover display with HTML formatting.
1649+ """
1650+ hovertemplate = f"Ball Bearing at Node: { self .n } <br>"
1651+ if self .tag is not None :
1652+ hovertemplate = f"Tag: { self .tag } <br>" + hovertemplate
1653+
1654+ hovertemplate += (
1655+ f"Number of Balls: { self .n_balls :.0f} <br>"
1656+ f"Ball Diameter: { self .d_balls :.4f} m<br>"
1657+ f"Static Load: { self .fs :.2f} N<br>"
1658+ f"Contact Angle: { self .alpha :.3f} rad<br>"
1659+ f"Kxx: { self .kxx [0 ]:.3e} N/m<br>"
1660+ f"Kyy: { self .kyy [0 ]:.3e} N/m<br>"
1661+ f"Cxx: { self .cxx [0 ]:.3e} N·s/m<br>"
1662+ f"Cyy: { self .cyy [0 ]:.3e} N·s/m<br>"
1663+ )
1664+
1665+ customdata = [self .n ]
1666+
1667+ return customdata , hovertemplate
1668+
14931669
14941670class RollerBearingElement (BearingElement ):
14951671 """A bearing element for roller bearings.
@@ -1805,6 +1981,46 @@ def __init__(
18051981 color = color ,
18061982 )
18071983
1984+ def _hover_info (self , frequency = None ):
1985+ """Generate hover information for magnetic bearing element.
1986+
1987+ Overrides the base class method to include magnetic bearing-specific
1988+ electromagnetic and control parameters.
1989+
1990+ Parameters
1991+ ----------
1992+ frequency : float, optional
1993+ Frequency at which to display coefficients (rad/s).
1994+ Not used for magnetic bearings (frequency-independent).
1995+
1996+ Returns
1997+ -------
1998+ customdata : list
1999+ Data to attach to hover trace.
2000+ hovertemplate : str
2001+ Template string for hover display with HTML formatting.
2002+ """
2003+ hovertemplate = f"Magnetic Bearing at Node: { self .n } <br>"
2004+ if self .tag is not None :
2005+ hovertemplate = f"Tag: { self .tag } <br>" + hovertemplate
2006+
2007+ hovertemplate += (
2008+ f"Air Gap (g0): { self .g0 :.4e} m<br>"
2009+ f"Bias Current (i0): { self .i0 :.2f} A<br>"
2010+ f"Pole Area: { self .ag :.4e} m²<br>"
2011+ f"Windings: { self .nw :.0f} <br>"
2012+ f"PID Kp: { self .kp_pid :.3e} <br>"
2013+ f"PID Kd: { self .kd_pid :.3e} <br>"
2014+ f"Kxx: { self .kxx [0 ]:.3e} N/m<br>"
2015+ f"Kyy: { self .kyy [0 ]:.3e} N/m<br>"
2016+ f"Cxx: { self .cxx [0 ]:.3e} N·s/m<br>"
2017+ f"Cyy: { self .cyy [0 ]:.3e} N·s/m<br>"
2018+ )
2019+
2020+ customdata = [self .n ]
2021+
2022+ return customdata , hovertemplate
2023+
18082024 def compute_pid_amb (self , dt , current_offset , setpoint , disp , dof_index ):
18092025 """Compute PID control force for a single AMB DoF (x or y).
18102026
@@ -2080,6 +2296,59 @@ def __init__(
20802296 ** kwargs ,
20812297 )
20822298
2299+ def _hover_info (self , frequency = None ):
2300+ """Generate hover information for cylindrical bearing element.
2301+
2302+ Overrides the base class method to include cylindrical bearing-specific
2303+ fluid film parameters and operating conditions.
2304+
2305+ Parameters
2306+ ----------
2307+ frequency : float, optional
2308+ Frequency at which to display coefficients (rad/s).
2309+ Not used - displays coefficients at first and last speeds.
2310+
2311+ Returns
2312+ -------
2313+ customdata : list
2314+ Data to attach to hover trace.
2315+ hovertemplate : str
2316+ Template string for hover display with HTML formatting.
2317+ """
2318+ # Get first and last speeds
2319+ freq_0 = self .speed [0 ]
2320+ freq_1 = self .speed [- 1 ]
2321+ idx_0 = 0
2322+ idx_1 = - 1
2323+
2324+ # Convert speeds to RPM
2325+ freq_0_rpm = Q_ (freq_0 , "rad/s" ).to ("RPM" ).m
2326+ freq_1_rpm = Q_ (freq_1 , "rad/s" ).to ("RPM" ).m
2327+
2328+ hovertemplate = f"Cylindrical Bearing at Node: { self .n } <br>"
2329+ if self .tag is not None :
2330+ hovertemplate = f"Tag: { self .tag } <br>" + hovertemplate
2331+
2332+ hovertemplate += (
2333+ f"Length: { self .bearing_length :.4f} m<br>"
2334+ f"Journal Diameter: { self .journal_diameter :.4f} m<br>"
2335+ f"Radial Clearance: { self .radial_clearance :.4e} m<br>"
2336+ f"Oil Viscosity: { self .oil_viscosity :.4f} Pa·s<br>"
2337+ f"Load: { self .weight :.2f} N<br>"
2338+ f"Speed: { freq_0_rpm :.2f} ... { freq_1_rpm :.2f} RPM<br>"
2339+ f"Eccentricity: { self .eccentricity [idx_0 ]:.4f} ... { self .eccentricity [idx_1 ]:.4f} <br>"
2340+ f"Attitude Angle: { self .attitude_angle [idx_0 ]:.4f} ... { self .attitude_angle [idx_1 ]:.4f} rad<br>"
2341+ f"Sommerfeld: { self .sommerfeld [idx_0 ]:.3e} ... { self .sommerfeld [idx_1 ]:.3e} <br>"
2342+ f"Kxx: { self .kxx_interpolated (freq_0 ):.3e} ... { self .kxx_interpolated (freq_1 ):.3e} N/m<br>"
2343+ f"Kyy: { self .kyy_interpolated (freq_0 ):.3e} ... { self .kyy_interpolated (freq_1 ):.3e} N/m<br>"
2344+ f"Cxx: { self .cxx_interpolated (freq_0 ):.3e} ... { self .cxx_interpolated (freq_1 ):.3e} N·s/m<br>"
2345+ f"Cyy: { self .cyy_interpolated (freq_0 ):.3e} ... { self .cyy_interpolated (freq_1 ):.3e} N·s/m<br>"
2346+ )
2347+
2348+ customdata = [self .n ]
2349+
2350+ return customdata , hovertemplate
2351+
20832352
20842353def bearing_example ():
20852354 """Create an example of bearing element.
0 commit comments