Skip to content

Commit bcb080e

Browse files
Refactor tooltip handling in TmfCommonXLineChart
Refactor TmfCommonXLineChartTooltipProvider to make the tooltip population logic extensible for subclasses via protected hooks, while preserving the default tooltip behavior. Also remove unused maxLen calculation that was left over from an earlier implementation. Files: - org.eclipse.tracecompass.tmf.ui.viewers.xychart.linechart.TmfCommonXLineChartTooltipProvider
1 parent d2fde0d commit bcb080e

5 files changed

Lines changed: 219 additions & 79 deletions

File tree

tmf/org.eclipse.tracecompass.tmf.ui/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %Bundle-Name
44
Bundle-Vendor: %Bundle-Vendor
5-
Bundle-Version: 9.2.0.qualifier
5+
Bundle-Version: 9.3.0.qualifier
66
Bundle-Localization: plugin
77
Bundle-SymbolicName: org.eclipse.tracecompass.tmf.ui;singleton:=true
88
Bundle-Activator: org.eclipse.tracecompass.internal.tmf.ui.Activator

tmf/org.eclipse.tracecompass.tmf.ui/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
</parent>
2121

2222
<artifactId>org.eclipse.tracecompass.tmf.ui</artifactId>
23-
<version>9.2.0-SNAPSHOT</version>
23+
<version>9.3.0-SNAPSHOT</version>
2424

2525
<packaging>eclipse-plugin</packaging>
2626

tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/tree/AbstractSelectTreeViewer3.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
* to allow filtering based on a specific column index.
2626
*
2727
* @author Hong Anh
28-
* @since 9.2
28+
* @since 9.3
2929
*/
3030
public abstract class AbstractSelectTreeViewer3 extends AbstractSelectTreeViewer2 {
3131
/**
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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
@@ -10,14 +10,19 @@
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

1517
package org.eclipse.tracecompass.tmf.ui.viewers.xychart.linechart;
1618

1719
import java.text.Format;
1820
import java.util.Arrays;
1921
import java.util.List;
22+
import java.util.Objects;
23+
import java.util.function.BiConsumer;
2024

25+
import org.eclipse.jdt.annotation.NonNull;
2126
import org.eclipse.swt.events.MouseEvent;
2227
import org.eclipse.swt.graphics.Color;
2328
import org.eclipse.swt.graphics.Point;
@@ -28,6 +33,7 @@
2833
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
2934
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
3035
import org.eclipse.tracecompass.tmf.ui.viewers.TmfAbstractToolTipHandler;
36+
import org.eclipse.tracecompass.tmf.ui.viewers.TmfAbstractToolTipHandler.ToolTipString;
3137
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.IAxis;
3238
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.ITmfChartTimeProvider;
3339
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.IXYSeries;
@@ -43,80 +49,8 @@
4349
*/
4450
public 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.3
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.3
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.3
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.3
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
}

tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/dialogs/MultiTreePatternFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ protected boolean isLeafMatch(Viewer viewer, Object element) {
8989
/**
9090
* Returns the index of the column used for filtering.
9191
* @return the column index (default is 0)
92-
* @since 9.2
92+
* @since 9.3
9393
*/
9494
protected int getIndexColumnFilter() {
9595
return 0;

0 commit comments

Comments
 (0)