Skip to content

Commit fd9280a

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 fd9280a

1 file changed

Lines changed: 210 additions & 74 deletions

File tree

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

Lines changed: 210 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.text.Format;
1818
import java.util.Arrays;
19+
import java.util.function.BiConsumer;
1920
import java.util.List;
2021

2122
import org.eclipse.swt.events.MouseEvent;
@@ -28,6 +29,7 @@
2829
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
2930
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
3031
import org.eclipse.tracecompass.tmf.ui.viewers.TmfAbstractToolTipHandler;
32+
import org.eclipse.tracecompass.tmf.ui.viewers.TmfAbstractToolTipHandler.ToolTipString;
3133
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.IAxis;
3234
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.ITmfChartTimeProvider;
3335
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.IXYSeries;
@@ -43,80 +45,8 @@
4345
*/
4446
public class TmfCommonXLineChartTooltipProvider extends TmfBaseProvider {
4547

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();
48+
private static final String HTML_COLOR_TOOLTIP = "<span style=\"color:%s;\">%s</span>"; //$NON-NLS-1$
49+
private final CommonToolTipHandler fToolTipHandler = new CommonToolTipHandler();
12050

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

0 commit comments

Comments
 (0)