11/*******************************************************************************
2- * Copyright (c) 2014, 2020 École Polytechnique de Montréal and others
2+ * Copyright (c) 2014, 2026 École Polytechnique de Montréal and others
33 *
44 * All rights reserved. This program and the accompanying materials are
55 * made available under the terms of the Eclipse Public License 2.0 which
1010 *
1111 * Contributors:
1212 * Geneviève Bastien - Initial API and implementation
13+ * Tran Hoang Thanh Duy - Refactor tooltip population for subclass
14+ * extensibility and remove unused maxLen calculation
1315 *******************************************************************************/
1416
1517package org .eclipse .tracecompass .tmf .ui .viewers .xychart .linechart ;
1618
1719import java .text .Format ;
1820import java .util .Arrays ;
1921import java .util .List ;
22+ import java .util .Objects ;
23+ import java .util .function .BiConsumer ;
2024
25+ import org .eclipse .jdt .annotation .NonNull ;
2126import org .eclipse .swt .events .MouseEvent ;
2227import org .eclipse .swt .graphics .Color ;
2328import org .eclipse .swt .graphics .Point ;
2833import org .eclipse .tracecompass .tmf .core .timestamp .ITmfTimestamp ;
2934import org .eclipse .tracecompass .tmf .core .timestamp .TmfTimestamp ;
3035import org .eclipse .tracecompass .tmf .ui .viewers .TmfAbstractToolTipHandler ;
36+ import org .eclipse .tracecompass .tmf .ui .viewers .TmfAbstractToolTipHandler .ToolTipString ;
3137import org .eclipse .tracecompass .tmf .ui .viewers .xychart .IAxis ;
3238import org .eclipse .tracecompass .tmf .ui .viewers .xychart .ITmfChartTimeProvider ;
3339import org .eclipse .tracecompass .tmf .ui .viewers .xychart .IXYSeries ;
4349 */
4450public class TmfCommonXLineChartTooltipProvider extends TmfBaseProvider {
4551
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 ();
52+ private static final String HTML_COLOR_TOOLTIP = "<span style=\" color:%s;\" >%s</span>" ; //$NON-NLS-1$
53+ private final CommonToolTipHandler fToolTipHandler = new CommonToolTipHandler ();
12054
12155 /**
12256 * Constructor for the tooltip provider
@@ -142,4 +76,210 @@ public TmfAbstractToolTipHandler getTooltipHandler() {
14276 public void refresh () {
14377 // nothing to do
14478 }
79+
80+ /**
81+ * Adds tooltip items that are not tied to a particular series (for example, the
82+ * timestamp corresponding to the hovered X coordinate).
83+ * <p>
84+ * This method is part of the provider API and is designed to be overridden by
85+ * subclasses that want to contribute additional context to the tooltip when the
86+ * user hovers the chart.
87+ * </p>
88+ * <p>
89+ * The default implementation adds a single "time" item computed from
90+ * {@code xCoordinate} and the chart viewer's time offset.
91+ * </p>
92+ *
93+ * @param adder
94+ * Callback used to append items to the tooltip.
95+ * @param xCoordinate
96+ * The hovered X coordinate in data space (i.e., in the same domain as
97+ * the series X values, typically nanoseconds relative to the viewer's
98+ * time offset).
99+ * @since 9.2
100+ */
101+ protected void addAdditionalTooltipItems (BiConsumer <@ NonNull ToolTipString , @ NonNull ToolTipString > adder , double xCoordinate ) {
102+ long timeNanos = Math .round (xCoordinate ) + getChartViewer ().getTimeOffset ();
103+ ITmfTimestamp time = TmfTimestamp .fromNanos (timeNanos );
104+ adder .accept (
105+ ToolTipString .fromString (Objects .requireNonNull (Messages .TmfCommonXLineChartTooltipProvider_time )),
106+ ToolTipString .fromTimestamp (Objects .requireNonNull (time .toString ()), time .toNanos ()));
107+ }
108+
109+ /**
110+ * Adds a tooltip item for a given series at the specified hovered index.
111+ * <p>
112+ * This method is part of the provider API and may be overridden to customize
113+ * how a series is rendered in the tooltip (e.g., formatting, units, hiding
114+ * specific series, etc.).
115+ * </p>
116+ * <p>
117+ * The default implementation:
118+ * <ul>
119+ * <li>computes a label from {@link #formatSeriesLabel(IXYSeries)} (including
120+ * the series color when available)</li>
121+ * <li>reads the Y value at {@code index}</li>
122+ * <li>formats it using {@code format} when non-null, otherwise uses a default
123+ * decimal representation</li>
124+ * </ul>
125+ * </p>
126+ *
127+ * @param adder
128+ * Callback used to append the key/value pair to the tooltip.
129+ * @param xySeries
130+ * The series for which to add a tooltip entry.
131+ * @param index
132+ * Hovered point index within the series arrays.
133+ * @param format
134+ * Optional numeric formatter (typically inherited from the chart Y axis tick
135+ * formatter). If {@code null}, a default decimal formatting is used.
136+ * @since 9.2
137+ */
138+ protected void addSeriesTooltipItem (BiConsumer <@ NonNull ToolTipString , @ NonNull ToolTipString > adder , IXYSeries xySeries , int index , Format format ) {
139+ double [] ySeries = xySeries .getYSeries ();
140+ if (ySeries == null || index < 0 || index >= ySeries .length ) {
141+ return ;
142+ }
143+
144+ String label = formatSeriesLabel (xySeries );
145+ double yValue = ySeries [index ];
146+ if (format == null ) {
147+ adder .accept (
148+ ToolTipString .fromHtml (label ),
149+ ToolTipString .fromDecimal (yValue ));
150+ } else {
151+ adder .accept (
152+ ToolTipString .fromHtml (label ),
153+ ToolTipString .fromString (Objects .requireNonNull (format .format (Objects .requireNonNull (yValue )))));
154+ }
155+ }
156+
157+ /**
158+ * Builds the display label for a series when shown in the tooltip.
159+ * <p>
160+ * The default implementation uses the series id and, when the series color is
161+ * available, wraps the label in an HTML span using that color so the tooltip
162+ * visually matches the series.
163+ * </p>
164+ *
165+ * @param xySeries
166+ * The series to format.
167+ * @return The formatted label (potentially containing HTML).
168+ * @since 9.2
169+ */
170+ protected static final @ NonNull String formatSeriesLabel (IXYSeries xySeries ) {
171+ String label = xySeries .getId ();
172+ if (label == null ) {
173+ label = "" ; //$NON-NLS-1$
174+ }
175+
176+ Color color = xySeries .getColor ();
177+ if (color != null ) {
178+ RGBA rgba = color .getRGBA ();
179+ RGBAColor rgbaColor = new RGBAColor (rgba .rgb .red , rgba .rgb .green , rgba .rgb .blue , rgba .alpha );
180+ label = String .format (TmfCommonXLineChartTooltipProvider .HTML_COLOR_TOOLTIP , rgbaColor , label );
181+ }
182+
183+ return label ;
184+ }
185+
186+ /**
187+ * Returns the id (key) of the first series that contributed an entry to the most
188+ * recently built tooltip.
189+ * <p>
190+ * This value is set when the tooltip is filled and can be used by subclasses to
191+ * correlate additional tooltip content with the first visible/valid series at the
192+ * hovered index.
193+ * </p>
194+ *
195+ * @return The id of the first valid series used for the tooltip, or {@code null}
196+ * if the tooltip has not been computed yet or no valid series was found.
197+ * @since 9.2
198+ */
199+ protected final String getFirstValidSeriesKey () {
200+ return fToolTipHandler .firstValidSeriesKey ;
201+ }
202+
203+ // ======================================================================
204+ // TOOLTIP HANDLER
205+ // ======================================================================
206+
207+ private final class CommonToolTipHandler extends TmfAbstractToolTipHandler {
208+
209+ private String firstValidSeriesKey ;
210+
211+ private CommonToolTipHandler () {
212+ firstValidSeriesKey = null ;
213+ }
214+
215+ @ Override
216+ public void fill (Control control , MouseEvent event , Point pt ) {
217+ if (!isTooltipAvailable ()) {
218+ return ;
219+ }
220+
221+ IAxis xAxis = getXAxis ();
222+ double xCoordinate = xAxis .getDataCoordinate (pt .x );
223+ if (xCoordinate < 0 ) {
224+ return ;
225+ }
226+
227+ List <IXYSeries > series = getSeries ();
228+ int index = getHoveredIndex (series , xCoordinate );
229+ if (index < 0 ) {
230+ return ;
231+ }
232+
233+ Format format = null ;
234+ if (getChartViewer () instanceof TmfCommonXAxisChartViewer chartViewer ) {
235+ format = chartViewer .getSwtChart ().getAxisSet ().getYAxes ()[0 ].getTick ().getFormat ();
236+ }
237+
238+ boolean firstValid = true ;
239+ for (IXYSeries xySeries : series ) {
240+ if (!isValidSeriesIndex (xySeries , index )) {
241+ continue ;
242+ }
243+
244+ if (firstValid ) {
245+ firstValid = false ;
246+ firstValidSeriesKey = xySeries .getId ();
247+ addAdditionalTooltipItems ((key , value ) -> addItem (null , key , value ), xCoordinate );
248+ }
249+
250+ addSeriesTooltipItem ((key , value ) -> addItem (null , key , value ), xySeries , index , format );
251+ }
252+ }
253+
254+ private boolean isTooltipAvailable () {
255+ return getChartViewer ().getWindowDuration () != 0 ;
256+ }
257+
258+ private int getHoveredIndex (List <IXYSeries > series , double xCoordinate ) {
259+ if (series .isEmpty ()) {
260+ return -1 ;
261+ }
262+
263+ double [] xSeries = series .get (0 ).getXSeries ();
264+ if (xSeries == null || xSeries .length == 0 ) {
265+ return -1 ;
266+ }
267+
268+ int index = Arrays .binarySearch (xSeries , xCoordinate );
269+ if (index < 0 ) {
270+ index = -index - 1 ;
271+ index = Math .max (0 , index - 1 );
272+ }
273+
274+ return index < xSeries .length ? index : -1 ;
275+ }
276+
277+ private boolean isValidSeriesIndex (IXYSeries series , int index ) {
278+ double [] ySeries = series .getYSeries ();
279+ return series .isVisible ()
280+ && ySeries != null
281+ && index >= 0
282+ && index < ySeries .length ;
283+ }
284+ }
145285}
0 commit comments