Skip to content

Commit 64dd8d6

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 64dd8d6

1 file changed

Lines changed: 227 additions & 74 deletions

File tree

tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/xychart/linechart/TmfCommonXLineChartTooltipProvider.java

Lines changed: 227 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
2929
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
3030
import org.eclipse.tracecompass.tmf.ui.viewers.TmfAbstractToolTipHandler;
31+
import org.eclipse.tracecompass.tmf.ui.viewers.TmfAbstractToolTipHandler.ToolTipString;
3132
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.IAxis;
3233
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.ITmfChartTimeProvider;
3334
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.IXYSeries;
@@ -43,80 +44,8 @@
4344
*/
4445
public 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

Comments
 (0)