tmf.ui: Add a SWTbot condition for XY charts ready
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / viewers / xycharts / linecharts / TmfCommonXLineChartViewer.java
1 /*******************************************************************************
2 * Copyright (c) 2014, 2015 École Polytechnique de Montréal and others.
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Geneviève Bastien - Initial API and implementation
11 *******************************************************************************/
12
13 package org.eclipse.tracecompass.tmf.ui.viewers.xycharts.linecharts;
14
15 import java.util.LinkedHashMap;
16 import java.util.Map;
17 import java.util.Map.Entry;
18 import java.util.concurrent.atomic.AtomicInteger;
19 import java.util.logging.Logger;
20
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.core.runtime.NullProgressMonitor;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.swt.SWT;
25 import org.eclipse.swt.graphics.Point;
26 import org.eclipse.swt.widgets.Composite;
27 import org.eclipse.swt.widgets.Display;
28 import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
29 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
30 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
31 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
32 import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
33 import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.TmfChartTimeStampFormat;
34 import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.TmfXYChartViewer;
35 import org.swtchart.IAxisTick;
36 import org.swtchart.ILineSeries;
37 import org.swtchart.ILineSeries.PlotSymbolType;
38 import org.swtchart.ISeries;
39 import org.swtchart.ISeries.SeriesType;
40 import org.swtchart.ISeriesSet;
41 import org.swtchart.LineStyle;
42 import org.swtchart.Range;
43
44 /**
45 * Abstract line chart viewer class implementation. All series in this viewer
46 * use the same X axis values. They are automatically created as values are
47 * provided for a key. Series by default will be displayed as a line. Each
48 * series appearance can be overridden when creating it.
49 *
50 * @author - Geneviève Bastien
51 */
52 public abstract class TmfCommonXLineChartViewer extends TmfXYChartViewer {
53
54 private static final double DEFAULT_MAXY = Double.MIN_VALUE;
55 private static final double DEFAULT_MINY = Double.MAX_VALUE;
56
57 /* The desired number of points per pixel */
58 private static final double RESOLUTION = 1.0;
59 private static final Logger LOGGER = TraceCompassLog.getLogger(TmfCommonXLineChartViewer.class);
60 private static final String LOG_STRING_WITH_PARAM = "[TmfCommonXLineChart:%s] viewerId=%s, %s"; //$NON-NLS-1$
61 private static final String LOG_STRING = "[TmfCommonXLineChart:%s] viewerId=%s"; //$NON-NLS-1$
62
63 private static final int[] LINE_COLORS = { SWT.COLOR_BLUE, SWT.COLOR_RED, SWT.COLOR_GREEN,
64 SWT.COLOR_MAGENTA, SWT.COLOR_CYAN,
65 SWT.COLOR_DARK_BLUE, SWT.COLOR_DARK_RED, SWT.COLOR_DARK_GREEN,
66 SWT.COLOR_DARK_MAGENTA, SWT.COLOR_DARK_CYAN, SWT.COLOR_DARK_YELLOW,
67 SWT.COLOR_BLACK, SWT.COLOR_GRAY };
68 private static final LineStyle[] LINE_STYLES = { LineStyle.SOLID, LineStyle.DASH, LineStyle.DOT, LineStyle.DASHDOT };
69
70 private final Map<String, double[]> fSeriesValues = new LinkedHashMap<>();
71 private double[] fXValues;
72 private double fResolution;
73
74 private UpdateThread fUpdateThread;
75
76 private volatile AtomicInteger fDirty = new AtomicInteger();
77
78 /**
79 * Constructor
80 *
81 * @param parent
82 * The parent composite
83 * @param title
84 * The title of the viewer
85 * @param xLabel
86 * The label of the xAxis
87 * @param yLabel
88 * The label of the yAXIS
89 */
90 public TmfCommonXLineChartViewer(Composite parent, String title, String xLabel, String yLabel) {
91 super(parent, title, xLabel, yLabel);
92 getSwtChart().getTitle().setVisible(false);
93 getSwtChart().getLegend().setPosition(SWT.BOTTOM);
94 getSwtChart().getAxisSet().getXAxes()[0].getTitle().setVisible(false);
95 setResolution(RESOLUTION);
96 setTooltipProvider(new TmfCommonXLineChartTooltipProvider(this));
97 }
98
99 /**
100 * Set the number of requests per pixel that should be done on this chart
101 *
102 * @param resolution
103 * The number of points per pixels
104 */
105 protected void setResolution(double resolution) {
106 fResolution = resolution;
107 }
108
109 @Override
110 public void loadTrace(ITmfTrace trace) {
111 super.loadTrace(trace);
112 reinitialize();
113 }
114
115 /**
116 * Formats a log message for this class
117 *
118 * @param event
119 * The event to log, that will be appended to the class name to
120 * make the full event name
121 * @param parameters
122 * The string of extra parameters to add to the log message, in
123 * the format name=value[, name=value]*, or <code>null</code> for
124 * no params
125 * @return The complete log message for this class
126 */
127 private String getLogMessage(String event, @Nullable String parameters) {
128 if (parameters == null) {
129 return String.format(LOG_STRING, event, getClass().getName());
130 }
131 return String.format(LOG_STRING_WITH_PARAM, event, getClass().getName(), parameters);
132 }
133
134 /**
135 * Forces a reinitialization of the data sources, even if it has already
136 * been initialized for this trace before
137 */
138 protected void reinitialize() {
139 fSeriesValues.clear();
140 fDirty.incrementAndGet();
141 Thread thread = new Thread() {
142 // Don't use TmfUiRefreshHandler (bug 467751)
143 @Override
144 public void run() {
145 LOGGER.info(() -> getLogMessage("InitializeThreadStart", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
146 initializeDataSource();
147 if (!getSwtChart().isDisposed()) {
148 getDisplay().asyncExec(new Runnable() {
149 @Override
150 public void run() {
151 if (!getSwtChart().isDisposed()) {
152 /* Delete the old series */
153 clearContent();
154 createSeries();
155 fDirty.decrementAndGet();
156 }
157 }
158 });
159 }
160 LOGGER.info(() -> getLogMessage("InitializeThreadEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
161 }
162 };
163 thread.start();
164 }
165
166 /**
167 * Initialize the source of the data for this viewer. This method is run in
168 * a separate thread, so this is where for example one can execute an
169 * analysis module and wait for its completion to initialize the series
170 */
171 protected void initializeDataSource() {
172
173 }
174
175 private class UpdateThread extends Thread {
176 private final IProgressMonitor fMonitor;
177 private final int fNumRequests;
178
179 public UpdateThread(int numRequests) {
180 super("Line chart update"); //$NON-NLS-1$
181 fNumRequests = numRequests;
182 fMonitor = new NullProgressMonitor();
183 }
184
185 @Override
186 public void run() {
187 LOGGER.info(() -> getLogMessage("UpdateThreadStart", "numRequests=" + fNumRequests + ", tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
188 Display.getDefault().syncExec(new Runnable() {
189 @Override
190 public void run() {
191 LOGGER.info(() -> getLogMessage("UpdateDataStart", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
192 updateData(getWindowStartTime(), getWindowEndTime(), fNumRequests, fMonitor);
193 fDirty.decrementAndGet();
194 LOGGER.info(() -> getLogMessage("UpdateDataEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
195 }
196 });
197 updateThreadFinished(this);
198 LOGGER.info(() -> getLogMessage("UpdateThreadEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
199 }
200
201 public void cancel() {
202 LOGGER.info(() -> getLogMessage("UpdateThreadCanceled", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
203 fMonitor.setCanceled(true);
204 }
205 }
206
207 private synchronized void newUpdateThread() {
208 cancelUpdate();
209 if (!getSwtChart().isDisposed()) {
210 final int numRequests = (int) (getSwtChart().getPlotArea().getBounds().width * fResolution);
211 fUpdateThread = new UpdateThread(numRequests);
212 fUpdateThread.start();
213 }
214 }
215
216 private synchronized void updateThreadFinished(UpdateThread thread) {
217 if (thread == fUpdateThread) {
218 fUpdateThread = null;
219 }
220 }
221
222 /**
223 * Cancels the currently running update thread. It is automatically called
224 * when the content is updated, but child viewers may want to call it
225 * manually to do some operations before calling
226 * {@link TmfCommonXLineChartViewer#updateContent}
227 */
228 protected synchronized void cancelUpdate() {
229 if (fUpdateThread != null) {
230 fUpdateThread.cancel();
231 }
232 }
233
234 @Override
235 protected void updateContent() {
236 fDirty.incrementAndGet();
237 getDisplay().asyncExec(new Runnable() {
238 @Override
239 public void run() {
240 newUpdateThread();
241 }
242 });
243 }
244
245 /**
246 * Convenience method to compute the values of the X axis for a given time
247 * range. This method will return at most nb values, equally separated from
248 * start to end. The step between values will be at least 1.0, so the number
249 * of values returned can be lower than nb.
250 *
251 * The returned time values are in internal time, ie to get trace time, the
252 * time offset needs to be added to those values.
253 *
254 * @param start
255 * The start time of the time range
256 * @param end
257 * End time of the range
258 * @param nb
259 * The maximum number of steps in the x axis.
260 * @return The time values (converted to double) to match every step.
261 */
262 protected static final double[] getXAxis(long start, long end, int nb) {
263 long steps = (end - start);
264 int nbVals = nb;
265 if (steps < nb) {
266 nbVals = (int) steps;
267 if (nbVals <= 0) {
268 nbVals = 1;
269 }
270 }
271 double step = steps / (double) nbVals;
272
273 double timestamps[] = new double[nbVals];
274 double curTime = 1;
275 for (int i = 0; i < nbVals; i++) {
276 timestamps[i] = curTime;
277 curTime += step;
278 }
279 return timestamps;
280 }
281
282 /**
283 * Set the values of the x axis. There is only one array of values for the x
284 * axis for all series of a line chart so it needs to be set once here.
285 *
286 * @param xaxis
287 * The values for the x axis. The values must be in internal
288 * time, ie time offset have been subtracted from trace time
289 * values.
290 */
291 protected final void setXAxis(double[] xaxis) {
292 fXValues = xaxis;
293 }
294
295 /**
296 * Update the series data because the time range has changed. The x axis
297 * values for this data update can be computed using the
298 * {@link TmfCommonXLineChartViewer#getXAxis(long, long, int)} method which
299 * will return a list of uniformely separated time values.
300 *
301 * Each series values should be set by calling the
302 * {@link TmfCommonXLineChartViewer#setSeries(String, double[])}.
303 *
304 * This method is responsible for calling the
305 * {@link TmfCommonXLineChartViewer#updateDisplay()} when needed for the new
306 * values to be displayed.
307 *
308 * @param start
309 * The start time of the range for which the get the data
310 * @param end
311 * The end time of the range
312 * @param nb
313 * The number of 'points' in the chart.
314 * @param monitor
315 * The progress monitor object
316 */
317 protected abstract void updateData(long start, long end, int nb, IProgressMonitor monitor);
318
319 /**
320 * Set the data for a given series of the graph. The series does not need to
321 * be created before calling this, but it needs to have at least as many
322 * values as the x axis.
323 *
324 * If the series does not exist, it will automatically be created at display
325 * time, with the default values.
326 *
327 * @param seriesName
328 * The name of the series for which to set the values
329 * @param seriesValues
330 * The array of values for the series
331 */
332 protected void setSeries(String seriesName, double[] seriesValues) {
333 if (fXValues.length != seriesValues.length) {
334 throw new IllegalStateException("All series in list must be of length : " + fXValues.length); //$NON-NLS-1$
335 }
336 fSeriesValues.put(seriesName, seriesValues);
337 }
338
339 /**
340 * Add a new series to the XY line chart. By default, it is a simple solid
341 * line.
342 *
343 * @param seriesName
344 * The name of the series to create
345 * @return The series so that the concrete viewer can modify its properties
346 * if required
347 */
348 protected ILineSeries addSeries(String seriesName) {
349 ISeriesSet seriesSet = getSwtChart().getSeriesSet();
350 int seriesCount = seriesSet.getSeries().length;
351 ILineSeries series = (ILineSeries) seriesSet.createSeries(SeriesType.LINE, seriesName);
352 series.setVisible(true);
353 series.enableArea(false);
354 series.setLineStyle(LINE_STYLES[(seriesCount / (LINE_COLORS.length)) % LINE_STYLES.length]);
355 series.setSymbolType(PlotSymbolType.NONE);
356 series.setLineColor(Display.getDefault().getSystemColor(LINE_COLORS[seriesCount % LINE_COLORS.length]));
357 return series;
358 }
359
360 /**
361 * Delete a series from the chart and its values from the viewer.
362 *
363 * @param seriesName
364 * Name of the series to delete
365 */
366 protected void deleteSeries(String seriesName) {
367 ISeries series = getSwtChart().getSeriesSet().getSeries(seriesName);
368 if (series != null) {
369 getSwtChart().getSeriesSet().deleteSeries(series.getId());
370 }
371 fSeriesValues.remove(seriesName);
372 }
373
374 /**
375 * Update the chart's values before refreshing the viewer
376 */
377 protected void updateDisplay() {
378 Display.getDefault().asyncExec(new Runnable() {
379 final TmfChartTimeStampFormat tmfChartTimeStampFormat = new TmfChartTimeStampFormat(getTimeOffset());
380
381 @Override
382 public void run() {
383 if (!getSwtChart().isDisposed()) {
384 double[] xValues = fXValues;
385 if (xValues.length < 1) {
386 return;
387 }
388 double maxy = DEFAULT_MAXY;
389 double miny = DEFAULT_MINY;
390 for (Entry<String, double[]> entry : fSeriesValues.entrySet()) {
391 ILineSeries series = (ILineSeries) getSwtChart().getSeriesSet().getSeries(entry.getKey());
392 if (series == null) {
393 series = addSeries(entry.getKey());
394 }
395 series.setXSeries(xValues);
396 /* Find the minimal and maximum values in this series */
397 for (double value : entry.getValue()) {
398 maxy = Math.max(maxy, value);
399 miny = Math.min(miny, value);
400 }
401 series.setYSeries(entry.getValue());
402 }
403 if (maxy == DEFAULT_MAXY) {
404 maxy = 1.0;
405 }
406
407 IAxisTick xTick = getSwtChart().getAxisSet().getXAxis(0).getTick();
408 xTick.setFormat(tmfChartTimeStampFormat);
409
410 final double start = 0.0;
411 double end = getWindowEndTime() - getWindowStartTime();
412 getSwtChart().getAxisSet().getXAxis(0).setRange(new Range(start, end));
413 if (maxy > miny) {
414 getSwtChart().getAxisSet().getYAxis(0).setRange(new Range(miny, maxy));
415 }
416 getSwtChart().redraw();
417
418 if (isSendTimeAlignSignals()) {
419 // The width of the chart might have changed and its
420 // time axis might be misaligned with the other views
421 Point viewPos = TmfCommonXLineChartViewer.this.getParent().getParent().toDisplay(0, 0);
422 int axisPos = getSwtChart().toDisplay(0, 0).x + getPointAreaOffset();
423 int timeAxisOffset = axisPos - viewPos.x;
424 TmfTimeViewAlignmentInfo timeAlignmentInfo = new TmfTimeViewAlignmentInfo(getControl().getShell(), viewPos, timeAxisOffset);
425 TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(TmfCommonXLineChartViewer.this, timeAlignmentInfo, true));
426 }
427 }
428 }
429 });
430 }
431
432 /**
433 * Create the series once the initialization of the viewer's data source is
434 * done. Series do not need to be created before setting their values, but
435 * if their appearance needs to be customized, this method is a good place
436 * to do so. It is called only once per trace.
437 */
438 protected void createSeries() {
439
440 }
441
442 @Override
443 protected void clearContent() {
444 getSwtChart().getAxisSet().getXAxis(0).getTick().setFormat(null);
445 super.clearContent();
446 }
447
448 @Override
449 public boolean isDirty() {
450 boolean dirty = super.isDirty();
451
452 if (dirty) {
453 return dirty;
454 }
455
456 // Check the specific dirtiness of this view
457 return fDirty.get() != 0;
458 }
459
460 }
This page took 0.041285 seconds and 5 git commands to generate.