| /********************************************************************** |
| * Copyright (c) 2015 Ericsson |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Bernd Hufmann - Initial API and implementation |
| **********************************************************************/ |
| package org.eclipse.tracecompass.tmf.ui.viewers.xycharts; |
| |
| import java.util.Collections; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseMoveListener; |
| import org.eclipse.swt.events.MouseTrackListener; |
| import org.eclipse.swt.events.PaintEvent; |
| import org.eclipse.swt.events.PaintListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp; |
| import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; |
| import org.eclipse.tracecompass.tmf.ui.viewers.TmfAbstractToolTipHandler; |
| import org.swtchart.Chart; |
| import org.swtchart.IAxis; |
| import org.swtchart.ISeries; |
| |
| /** |
| * Abstract tooltip provider for xy chart viewers. It displays the y value and y |
| * value of the data point of the mouse position. Extending classes can provide |
| * a custom tooltip text. |
| * |
| * @author Bernd Hufmann |
| * @since 2.0 |
| */ |
| public class TmfClosestDataPointTooltipProvider extends TmfBaseProvider implements MouseTrackListener, MouseMoveListener, PaintListener { |
| |
| private static final @NonNull String OLD_TOOLTIP = ""; //$NON-NLS-1$ |
| |
| private final class XYToolTipHandler extends TmfAbstractToolTipHandler { |
| @Override |
| public void fill(Control control, MouseEvent e, Point pt) { |
| if ((getChartViewer().getWindowDuration() != 0) && (e != null)) { |
| Chart chart = getChart(); |
| IAxis xAxis = chart.getAxisSet().getXAxis(0); |
| IAxis yAxis = chart.getAxisSet().getYAxis(0); |
| |
| ISeries[] series = chart.getSeriesSet().getSeries(); |
| |
| double smallestDistance = Double.MAX_VALUE; |
| Parameter param = null; |
| |
| // go over all series |
| for (int k = 0; k < series.length; k++) { |
| ISeries serie = series[k]; |
| double[] xS = serie.getXSeries(); |
| double[] yS = serie.getYSeries(); |
| |
| if ((xS == null) || (yS == null)) { |
| continue; |
| } |
| // go over all data points |
| for (int i = 0; i < xS.length; i++) { |
| int xs = xAxis.getPixelCoordinate(xS[i]) - e.x; |
| int ys = yAxis.getPixelCoordinate(yS[i]) - e.y; |
| double currentDistance = xs * xs + ys * ys; |
| |
| /* |
| * Check for smallest distance to mouse position and |
| * only consider it if the mouse is close the data |
| * point. |
| */ |
| if ((currentDistance < smallestDistance) && (currentDistance < (HIGHLIGHT_RADIUS * HIGHLIGHT_RADIUS))) { |
| smallestDistance = currentDistance; |
| fHighlightX = xs + e.x; |
| fHighlightY = ys + e.y; |
| if (param == null) { |
| param = new Parameter(); |
| } |
| param.setSeriesIndex(k); |
| param.setDataIndex(i); |
| } |
| } |
| } |
| Map<String, Map<String, Object>> tooltip = null; |
| if (param != null) { |
| tooltip = createToolTipMap(param); |
| if (tooltip == null) { |
| return; |
| } |
| fIsHighlight = true; |
| chart.redraw(); |
| } |
| if (tooltip == null) { |
| return; |
| } |
| /* |
| * Note that tooltip might be null which will clear the previous |
| * tooltip string. This is intentional. |
| */ |
| for (Entry<String, Map<String, Object>> entry : tooltip.entrySet()) { |
| ToolTipString category = entry.getKey().isEmpty() || entry.getKey().equals(OLD_TOOLTIP) ? null : ToolTipString.fromString(entry.getKey()); |
| for (Entry<String, Object> secondEntry : entry.getValue().entrySet()) { |
| |
| Object value = secondEntry.getValue(); |
| String key = secondEntry.getKey(); |
| if (value instanceof Number) { |
| addItem(category, ToolTipString.fromString(key), ToolTipString.fromDecimal((Number) value)); |
| } else if (value instanceof ITmfTimestamp) { |
| addItem(category, ToolTipString.fromString(key), ToolTipString.fromTimestamp(String.valueOf(value), ((ITmfTimestamp) value).toNanos())); |
| } else { |
| addItem(category, ToolTipString.fromString(key), ToolTipString.fromString(String.valueOf(value))); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Constants |
| // ------------------------------------------------------------------------ |
| private static final int ALPHA = 128; |
| private static final int HIGHLIGHT_RADIUS = 5; |
| |
| // ------------------------------------------------------------------------ |
| // Attributes |
| // ------------------------------------------------------------------------ |
| /** X coordinate for highlighting */ |
| private int fHighlightX; |
| /** y coordinate for highlighting */ |
| private int fHighlightY; |
| /** Flag to do highlighting or not */ |
| private boolean fIsHighlight; |
| /** Tooltip handler */ |
| private TmfAbstractToolTipHandler fTooltipHandler = new XYToolTipHandler(); |
| |
| // ------------------------------------------------------------------------ |
| // Constructors |
| // ------------------------------------------------------------------------ |
| /** |
| * Constructor for a tool tip provider. |
| * |
| * @param tmfChartViewer |
| * - the parent chart viewer |
| */ |
| public TmfClosestDataPointTooltipProvider(ITmfChartTimeProvider tmfChartViewer) { |
| super(tmfChartViewer); |
| register(); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // TmfBaseProvider |
| // ------------------------------------------------------------------------ |
| @Override |
| public void register() { |
| Chart chart = getChart(); |
| chart.getPlotArea().addMouseMoveListener(this); |
| chart.getPlotArea().addPaintListener(this); |
| fTooltipHandler.activateHoverHelp(chart.getPlotArea()); |
| } |
| |
| @Override |
| public void deregister() { |
| |
| Chart chart = getChart(); |
| if ((chart != null) && !chart.isDisposed()) { |
| chart.getPlotArea().removeMouseMoveListener(this); |
| chart.getPlotArea().removePaintListener(this); |
| fTooltipHandler.deactivateHoverHelp(chart.getPlotArea()); |
| } |
| } |
| |
| @Override |
| public void refresh() { |
| // nothing to do |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MouseTrackListener |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * @deprecated, do not extend, use {@link #createToolTipMap(Parameter)} to |
| * populate tooltips |
| */ |
| @Deprecated |
| @Override |
| public void mouseEnter(MouseEvent e) { |
| // do nothing |
| } |
| |
| /** |
| * @deprecated, do not extend, use {@link #createToolTipMap(Parameter)} to |
| * populate tooltips |
| */ |
| @Deprecated |
| @Override |
| public void mouseExit(MouseEvent e) { |
| // do nothing |
| } |
| |
| /** |
| * @deprecated, do not extend, use {@link #createToolTipMap(Parameter)} to |
| * populate tooltips |
| */ |
| @Deprecated |
| @Override |
| public void mouseHover(MouseEvent e) { |
| // do nothing |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MouseMoveListener |
| // ------------------------------------------------------------------------ |
| @Override |
| public void mouseMove(@Nullable MouseEvent e) { |
| if (fIsHighlight) { |
| fIsHighlight = false; |
| getChart().redraw(); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // PaintListener |
| // ------------------------------------------------------------------------ |
| @Override |
| public void paintControl(PaintEvent e) { |
| if (fIsHighlight && e != null) { |
| e.gc.setBackground(Display.getDefault().getSystemColor( |
| SWT.COLOR_RED)); |
| e.gc.setAlpha(ALPHA); |
| |
| e.gc.fillOval(fHighlightX - HIGHLIGHT_RADIUS, fHighlightY - HIGHLIGHT_RADIUS, |
| 2 * HIGHLIGHT_RADIUS, 2 * HIGHLIGHT_RADIUS); |
| } |
| } |
| |
| /** |
| * Creates the tooltip based on the given parameter. |
| * |
| * @param param |
| * parameter to create the tooltip string |
| * @return the tooltip based on the given parameter. |
| * @deprecated use {@link #createToolTipMap(Parameter)} as it will |
| * categorize data better |
| */ |
| @Deprecated |
| protected String createToolTipText(@NonNull Parameter param) { |
| ISeries[] series = getChart().getSeriesSet().getSeries(); |
| int seriesIndex = param.getSeriesIndex(); |
| int dataIndex = param.getDataIndex(); |
| if ((series != null) && (seriesIndex < series.length)) { |
| ISeries serie = series[seriesIndex]; |
| double[] xS = serie.getXSeries(); |
| double[] yS = serie.getYSeries(); |
| if ((xS != null) && (yS != null) && (dataIndex < xS.length) && (dataIndex < yS.length)) { |
| StringBuilder buffer = new StringBuilder(); |
| buffer.append("x="); //$NON-NLS-1$ |
| buffer.append(TmfTimestamp.fromNanos((long) xS[dataIndex] + getChartViewer().getTimeOffset()).toString()); |
| buffer.append('\n'); |
| buffer.append("y="); //$NON-NLS-1$ |
| buffer.append((long) yS[dataIndex]); |
| return buffer.toString(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Creates the tooltip based on the given parameter. |
| * |
| * @param param |
| * parameter to create the tooltip string |
| * @return the tooltip map based on the given parameter. The Map<String, |
| * Map<String, Object>> can be seen as a table. The first element is |
| * the category, the second is the key, third is the value. |
| * @since 5.0 |
| */ |
| protected @Nullable Map<@NonNull String, @NonNull Map<@NonNull String, @NonNull Object>> createToolTipMap(@NonNull Parameter param) { |
| String toolTipText = createToolTipText(param); |
| if (toolTipText == null) { |
| return null; |
| } |
| return Collections.singletonMap(OLD_TOOLTIP, Collections.singletonMap(OLD_TOOLTIP, toolTipText)); |
| } |
| |
| /** |
| * Parameter class |
| */ |
| protected static class Parameter { |
| /* A series index */ |
| private int seriesIndex; |
| /* A data point index within a series */ |
| private int dataIndex; |
| |
| /** |
| * @return the series index |
| */ |
| public int getSeriesIndex() { |
| return seriesIndex; |
| } |
| |
| /** |
| * @param seriesIndex |
| * index the seriesIndex to set |
| */ |
| public void setSeriesIndex(int seriesIndex) { |
| this.seriesIndex = seriesIndex; |
| } |
| |
| /** |
| * @return the data index |
| */ |
| public int getDataIndex() { |
| return dataIndex; |
| } |
| |
| /** |
| * @param dataIndex |
| * the data index to set |
| */ |
| public void setDataIndex(int dataIndex) { |
| this.dataIndex = dataIndex; |
| } |
| |
| } |
| } |