2828import org .eclipse .tracecompass .tmf .core .timestamp .ITmfTimestamp ;
2929import org .eclipse .tracecompass .tmf .core .timestamp .TmfTimestamp ;
3030import org .eclipse .tracecompass .tmf .ui .viewers .TmfAbstractToolTipHandler ;
31+ import org .eclipse .tracecompass .tmf .ui .viewers .TmfAbstractToolTipHandler .ToolTipString ;
3132import org .eclipse .tracecompass .tmf .ui .viewers .xychart .IAxis ;
3233import org .eclipse .tracecompass .tmf .ui .viewers .xychart .ITmfChartTimeProvider ;
3334import org .eclipse .tracecompass .tmf .ui .viewers .xychart .IXYSeries ;
4344 */
4445public class TmfCommonXLineChartTooltipProvider extends TmfBaseProvider {
4546
46- private final class XYToolTipHandler extends TmfAbstractToolTipHandler {
47- private static final String HTML_COLOR_TOOLTIP = "<span style=\" color:%s;\" >%s</span>" ; //$NON-NLS-1$
48-
49- private boolean isValid (int index , IXYSeries serie ) {
50- double [] ySeries = serie .getYSeries ();
51- return serie .isVisible () && ySeries != null && ySeries .length > index ;
52- }
53-
54- @ Override
55- public void fill (Control control , MouseEvent event , Point pt ) {
56- if (getChartViewer ().getWindowDuration () != 0 ) {
57- IAxis xAxis = getXAxis ();
58-
59- double xCoordinate = xAxis .getDataCoordinate (pt .x );
60-
61- List <IXYSeries > series = getSeries ();
62-
63- if ((xCoordinate < 0 ) || (series .isEmpty ())) {
64- return ;
65- }
66-
67- /* Find the index of the value we want */
68- double [] xS = series .get (0 ).getXSeries ();
69- if (xS == null ) {
70- return ;
71- }
72- int index = Arrays .binarySearch (xS , xCoordinate );
73- index = index >= 0 ? index : -index - 1 ;
74- int maxLen = 0 ;
75- for (IXYSeries serie : series ) {
76- /* Make sure the series values and the value at index exist */
77- if (isValid (index , serie )) {
78- maxLen = Math .max (maxLen , serie .getId ().length ());
79- }
80- }
81-
82- TmfCommonXAxisChartViewer viewer = null ;
83- Format format = null ;
84- ITmfChartTimeProvider timeProvider = getChartViewer ();
85- if (timeProvider instanceof TmfCommonXAxisChartViewer ) {
86- viewer = (TmfCommonXAxisChartViewer ) timeProvider ;
87- format = viewer .getSwtChart ().getAxisSet ().getYAxes ()[0 ].getTick ().getFormat ();
88- }
89- ITmfTimestamp time = TmfTimestamp .fromNanos ((long ) xCoordinate + getChartViewer ().getTimeOffset ());
90- addItem (null , ToolTipString .fromString (Messages .TmfCommonXLineChartTooltipProvider_time ), ToolTipString .fromTimestamp (time .toString (), time .toNanos ()));
91- /* For each series, get the value at the index */
92- for (IXYSeries serie : series ) {
93- double [] yS = serie .getYSeries ();
94- /* Make sure the series values and the value at index exist */
95- if (isValid (index , serie )) {
96- String key = serie .getId ();
97- Color color = serie .getColor ();
98- if (key != null && color != null && viewer != null ) {
99- RGBA rgba = color .getRGBA ();
100- RGBAColor rgbaColor = new RGBAColor (rgba .rgb .red , rgba .rgb .green , rgba .rgb .blue , rgba .alpha );
101- key = String .format (HTML_COLOR_TOOLTIP , rgbaColor , key );
102- }
103- if (key == null ) {
104- key = "" ; //$NON-NLS-1$
105- }
106- double yValue = yS [index ];
107- if (format == null ) {
108- addItem (null , ToolTipString .fromHtml (key ), ToolTipString .fromDecimal (yValue ));
109- } else {
110- addItem (null , ToolTipString .fromHtml (key ), ToolTipString .fromString (format .format (yValue )));
111- }
112- }
113- }
114- }
115- }
116-
117- }
118-
119- private XYToolTipHandler fToolTipHandler = new XYToolTipHandler ();
47+ private static final String HTML_COLOR_TOOLTIP = "<span style=\" color:%s;\" >%s</span>" ; //$NON-NLS-1$
48+ private final CommonToolTipHandler fToolTipHandler = new CommonToolTipHandler ();
12049
12150 /**
12251 * Constructor for the tooltip provider
@@ -142,4 +71,228 @@ public TmfAbstractToolTipHandler getTooltipHandler() {
14271 public void refresh () {
14372 // nothing to do
14473 }
74+
75+ /**
76+ * Functional interface used to add items into the tooltip.
77+ * <p>
78+ * This abstraction allows tooltip content builders (including subclasses) to
79+ * contribute key/value pairs without depending directly on the underlying
80+ * tooltip UI implementation.
81+ * </p>
82+ * @since 9.2
83+ */
84+ @ FunctionalInterface
85+ protected interface ITooltipItemAdder {
86+ /**
87+ * Adds a key/value pair to the tooltip.
88+ *
89+ * @param key
90+ * The item key (label). Must not be {@code null}.
91+ * @param value
92+ * The item value. Must not be {@code null}.
93+ */
94+ void addItem (ToolTipString key , ToolTipString value );
95+ }
96+
97+ /**
98+ * Adds tooltip items that are not tied to a particular series (for example, the
99+ * timestamp corresponding to the hovered X coordinate).
100+ * <p>
101+ * This method is part of the provider API and is designed to be overridden by
102+ * subclasses that want to contribute additional context to the tooltip when the
103+ * user hovers the chart.
104+ * </p>
105+ * <p>
106+ * The default implementation adds a single "time" item computed from
107+ * {@code xCoordinate} and the chart viewer's time offset.
108+ * </p>
109+ *
110+ * @param adder
111+ * Callback used to append items to the tooltip.
112+ * @param xCoordinate
113+ * The hovered X coordinate in data space (i.e., in the same domain as
114+ * the series X values, typically nanoseconds relative to the viewer's
115+ * time offset).
116+ * @since 9.2
117+ */
118+ protected void addAdditionalTooltipItems (ITooltipItemAdder adder , double xCoordinate ) {
119+ long timeNanos = Math .round (xCoordinate ) + getChartViewer ().getTimeOffset ();
120+ ITmfTimestamp time = TmfTimestamp .fromNanos (timeNanos );
121+ adder .addItem (
122+ ToolTipString .fromString (Messages .TmfCommonXLineChartTooltipProvider_time ),
123+ ToolTipString .fromTimestamp (time .toString (), time .toNanos ()));
124+ }
125+
126+ /**
127+ * Adds a tooltip item for a given series at the specified hovered index.
128+ * <p>
129+ * This method is part of the provider API and may be overridden to customize
130+ * how a series is rendered in the tooltip (e.g., formatting, units, hiding
131+ * specific series, etc.).
132+ * </p>
133+ * <p>
134+ * The default implementation:
135+ * <ul>
136+ * <li>computes a label from {@link #formatSeriesLabel(IXYSeries)} (including
137+ * the series color when available)</li>
138+ * <li>reads the Y value at {@code index}</li>
139+ * <li>formats it using {@code format} when non-null, otherwise uses a default
140+ * decimal representation</li>
141+ * </ul>
142+ * </p>
143+ *
144+ * @param adder
145+ * Callback used to append the key/value pair to the tooltip.
146+ * @param xySeries
147+ * The series for which to add a tooltip entry.
148+ * @param index
149+ * Hovered point index within the series arrays.
150+ * @param format
151+ * Optional numeric formatter (typically inherited from the chart Y axis tick
152+ * formatter). If {@code null}, a default decimal formatting is used.
153+ * @since 9.2
154+ */
155+ protected void addSeriesTooltipItem (ITooltipItemAdder adder , IXYSeries xySeries , int index , Format format ) {
156+ double [] ySeries = xySeries .getYSeries ();
157+ if (ySeries == null || index < 0 || index >= ySeries .length ) {
158+ return ;
159+ }
160+
161+ String label = formatSeriesLabel (xySeries );
162+ double yValue = ySeries [index ];
163+ if (format == null ) {
164+ adder .addItem (
165+ ToolTipString .fromHtml (label ),
166+ ToolTipString .fromDecimal (yValue ));
167+ } else {
168+ adder .addItem (
169+ ToolTipString .fromHtml (label ),
170+ ToolTipString .fromString (format .format (yValue )));
171+ }
172+ }
173+
174+ /**
175+ * Builds the display label for a series when shown in the tooltip.
176+ * <p>
177+ * The default implementation uses the series id and, when the series color is
178+ * available, wraps the label in an HTML span using that color so the tooltip
179+ * visually matches the series.
180+ * </p>
181+ *
182+ * @param xySeries
183+ * The series to format.
184+ * @return The formatted label (potentially containing HTML).
185+ * @since 9.2
186+ */
187+ protected static final String formatSeriesLabel (IXYSeries xySeries ) {
188+ String label = xySeries .getId ();
189+ if (label == null ) {
190+ label = "" ; //$NON-NLS-1$
191+ }
192+
193+ Color color = xySeries .getColor ();
194+ if (color != null ) {
195+ RGBA rgba = color .getRGBA ();
196+ RGBAColor rgbaColor = new RGBAColor (rgba .rgb .red , rgba .rgb .green , rgba .rgb .blue , rgba .alpha );
197+ label = String .format (TmfCommonXLineChartTooltipProvider .HTML_COLOR_TOOLTIP , rgbaColor , label );
198+ }
199+
200+ return label ;
201+ }
202+
203+ /**
204+ * Returns the id (key) of the first series that contributed an entry to the most
205+ * recently built tooltip.
206+ * <p>
207+ * This value is set when the tooltip is filled and can be used by subclasses to
208+ * correlate additional tooltip content with the first visible/valid series at the
209+ * hovered index.
210+ * </p>
211+ *
212+ * @return The id of the first valid series used for the tooltip, or {@code null}
213+ * if the tooltip has not been computed yet or no valid series was found.
214+ * @since 9.2
215+ */
216+ protected final String getFirstValidSeriesKey () {
217+ return fToolTipHandler .firstValidSeriesKey ;
218+ }
219+
220+ // ======================================================================
221+ // TOOLTIP HANDLER
222+ // ======================================================================
223+
224+ private final class CommonToolTipHandler extends TmfAbstractToolTipHandler {
225+
226+ private String firstValidSeriesKey ;
227+
228+ @ Override
229+ public void fill (Control control , MouseEvent event , Point pt ) {
230+ if (!isTooltipAvailable ()) {
231+ return ;
232+ }
233+
234+ IAxis xAxis = getXAxis ();
235+ double xCoordinate = xAxis .getDataCoordinate (pt .x );
236+ if (xCoordinate < 0 ) {
237+ return ;
238+ }
239+
240+ List <IXYSeries > series = getSeries ();
241+ int index = getHoveredIndex (series , xCoordinate );
242+ if (index < 0 ) {
243+ return ;
244+ }
245+
246+ Format format = null ;
247+ if (getChartViewer () instanceof TmfCommonXAxisChartViewer chartViewer ) {
248+ format = chartViewer .getSwtChart ().getAxisSet ().getYAxes ()[0 ].getTick ().getFormat ();
249+ }
250+
251+ boolean firstValid = true ;
252+ for (IXYSeries xySeries : series ) {
253+ if (!isValidSeriesIndex (xySeries , index )) {
254+ continue ;
255+ }
256+
257+ if (firstValid ) {
258+ firstValid = false ;
259+ firstValidSeriesKey = xySeries .getId ();
260+ addAdditionalTooltipItems ((key , value ) -> addItem (null , key , value ), xCoordinate );
261+ }
262+
263+ addSeriesTooltipItem ((key , value ) -> addItem (null , key , value ), xySeries , index , format );
264+ }
265+ }
266+
267+ private boolean isTooltipAvailable () {
268+ return getChartViewer ().getWindowDuration () != 0 ;
269+ }
270+
271+ private int getHoveredIndex (List <IXYSeries > series , double xCoordinate ) {
272+ if (series .isEmpty ()) {
273+ return -1 ;
274+ }
275+
276+ double [] xSeries = series .get (0 ).getXSeries ();
277+ if (xSeries == null || xSeries .length == 0 ) {
278+ return -1 ;
279+ }
280+
281+ int index = Arrays .binarySearch (xSeries , xCoordinate );
282+ if (index < 0 ) {
283+ index = -index - 1 ;
284+ index = Math .max (0 , index - 1 );
285+ }
286+
287+ return index < xSeries .length ? index : -1 ;
288+ }
289+
290+ private boolean isValidSeriesIndex (IXYSeries series , int index ) {
291+ double [] ySeries = series .getYSeries ();
292+ return series .isVisible ()
293+ && ySeries != null
294+ && index >= 0
295+ && index < ySeries .length ;
296+ }
297+ }
145298}
0 commit comments