timing.ui: Add dirty conditions for SWTbot to scatter graph viewer
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / viewers / xycharts / linecharts / TmfCommonXLineChartViewer.java
CommitLineData
2e427755 1/*******************************************************************************
4e72adee 2 * Copyright (c) 2014, 2015 École Polytechnique de Montréal and others.
2e427755
GB
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
2bdf0193 13package org.eclipse.tracecompass.tmf.ui.viewers.xycharts.linecharts;
2e427755
GB
14
15import java.util.LinkedHashMap;
16import java.util.Map;
17import java.util.Map.Entry;
c27e9fec 18import java.util.concurrent.atomic.AtomicInteger;
381ebcb1 19import java.util.logging.Logger;
2e427755 20
00968516
GB
21import org.eclipse.core.runtime.IProgressMonitor;
22import org.eclipse.core.runtime.NullProgressMonitor;
381ebcb1 23import org.eclipse.jdt.annotation.Nullable;
c85a741e 24import org.eclipse.swt.SWT;
54404589 25import org.eclipse.swt.graphics.Point;
2e427755
GB
26import org.eclipse.swt.widgets.Composite;
27import org.eclipse.swt.widgets.Display;
381ebcb1 28import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
54404589 29import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
2bdf0193 30import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
54404589
MAL
31import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
32import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
2bdf0193
AM
33import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.TmfChartTimeStampFormat;
34import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.TmfXYChartViewer;
2e427755
GB
35import org.swtchart.IAxisTick;
36import org.swtchart.ILineSeries;
37import org.swtchart.ILineSeries.PlotSymbolType;
38import org.swtchart.ISeries;
39import org.swtchart.ISeries.SeriesType;
c85a741e 40import org.swtchart.ISeriesSet;
2e427755
GB
41import org.swtchart.LineStyle;
42import 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
2e427755
GB
51 */
52public 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;
381ebcb1
GB
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$
2e427755 62
c85a741e
GB
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
2e427755
GB
70 private final Map<String, double[]> fSeriesValues = new LinkedHashMap<>();
71 private double[] fXValues;
9392459a 72 private double fResolution;
2e427755 73
00968516
GB
74 private UpdateThread fUpdateThread;
75
e18d40d0 76 private final AtomicInteger fDirty = new AtomicInteger();
c27e9fec 77
2e427755
GB
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);
58bc31eb 92 getSwtChart().getTitle().setVisible(false);
b5e767ce
MK
93 getSwtChart().getLegend().setPosition(SWT.BOTTOM);
94 getSwtChart().getAxisSet().getXAxes()[0].getTitle().setVisible(false);
9392459a 95 setResolution(RESOLUTION);
2e427755
GB
96 setTooltipProvider(new TmfCommonXLineChartTooltipProvider(this));
97 }
98
9392459a
GB
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
2e427755
GB
109 @Override
110 public void loadTrace(ITmfTrace trace) {
111 super.loadTrace(trace);
47cb22a7
GB
112 reinitialize();
113 }
114
381ebcb1
GB
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
47cb22a7
GB
134 /**
135 * Forces a reinitialization of the data sources, even if it has already
136 * been initialized for this trace before
47cb22a7
GB
137 */
138 protected void reinitialize() {
2e427755 139 fSeriesValues.clear();
e18d40d0 140 /* Initializing data: the content is not current */
c27e9fec 141 fDirty.incrementAndGet();
2e427755 142 Thread thread = new Thread() {
5048e376 143 // Don't use TmfUiRefreshHandler (bug 467751)
2e427755
GB
144 @Override
145 public void run() {
381ebcb1 146 LOGGER.info(() -> getLogMessage("InitializeThreadStart", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
2e427755 147 initializeDataSource();
5048e376
BH
148 if (!getSwtChart().isDisposed()) {
149 getDisplay().asyncExec(new Runnable() {
150 @Override
151 public void run() {
152 if (!getSwtChart().isDisposed()) {
153 /* Delete the old series */
e18d40d0
GB
154 try {
155 clearContent();
156 createSeries();
157 } finally {
158 /* View is cleared, decrement fDirty */
159 fDirty.decrementAndGet();
160 }
5048e376 161 }
0573bfec 162 }
5048e376
BH
163 });
164 }
381ebcb1 165 LOGGER.info(() -> getLogMessage("InitializeThreadEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
2e427755
GB
166 }
167 };
168 thread.start();
169 }
170
171 /**
172 * Initialize the source of the data for this viewer. This method is run in
173 * a separate thread, so this is where for example one can execute an
174 * analysis module and wait for its completion to initialize the series
175 */
176 protected void initializeDataSource() {
177
178 }
179
00968516
GB
180 private class UpdateThread extends Thread {
181 private final IProgressMonitor fMonitor;
182 private final int fNumRequests;
183
184 public UpdateThread(int numRequests) {
185 super("Line chart update"); //$NON-NLS-1$
186 fNumRequests = numRequests;
187 fMonitor = new NullProgressMonitor();
188 }
189
190 @Override
191 public void run() {
381ebcb1 192 LOGGER.info(() -> getLogMessage("UpdateThreadStart", "numRequests=" + fNumRequests + ", tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
0573bfec
MK
193 Display.getDefault().syncExec(new Runnable() {
194 @Override
195 public void run() {
381ebcb1 196 LOGGER.info(() -> getLogMessage("UpdateDataStart", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
e18d40d0
GB
197 try {
198 updateData(getWindowStartTime(), getWindowEndTime(), fNumRequests, fMonitor);
199 } finally {
200 /*
201 * fDirty should have been incremented before creating
202 * the thread, so we decrement it once it is finished
203 */
204 fDirty.decrementAndGet();
205 LOGGER.info(() -> getLogMessage("UpdateDataEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
206 }
0573bfec
MK
207 }
208 });
00968516 209 updateThreadFinished(this);
381ebcb1 210 LOGGER.info(() -> getLogMessage("UpdateThreadEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
00968516
GB
211 }
212
213 public void cancel() {
381ebcb1 214 LOGGER.info(() -> getLogMessage("UpdateThreadCanceled", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
00968516
GB
215 fMonitor.setCanceled(true);
216 }
217 }
218
219 private synchronized void newUpdateThread() {
220 cancelUpdate();
8a328018
FLN
221 if (!getSwtChart().isDisposed()) {
222 final int numRequests = (int) (getSwtChart().getPlotArea().getBounds().width * fResolution);
223 fUpdateThread = new UpdateThread(numRequests);
224 fUpdateThread.start();
225 }
00968516
GB
226 }
227
228 private synchronized void updateThreadFinished(UpdateThread thread) {
229 if (thread == fUpdateThread) {
230 fUpdateThread = null;
231 }
232 }
233
234 /**
235 * Cancels the currently running update thread. It is automatically called
236 * when the content is updated, but child viewers may want to call it
237 * manually to do some operations before calling
238 * {@link TmfCommonXLineChartViewer#updateContent}
239 */
240 protected synchronized void cancelUpdate() {
241 if (fUpdateThread != null) {
242 fUpdateThread.cancel();
243 }
244 }
245
2e427755
GB
246 @Override
247 protected void updateContent() {
e18d40d0
GB
248 /*
249 * Content is not up to date, so we increment fDirty. It will be
250 * decremented at the end of the update thread
251 */
c27e9fec 252 fDirty.incrementAndGet();
2e427755 253 getDisplay().asyncExec(new Runnable() {
2e427755
GB
254 @Override
255 public void run() {
00968516 256 newUpdateThread();
2e427755
GB
257 }
258 });
259 }
260
261 /**
262 * Convenience method to compute the values of the X axis for a given time
5c75d7d2
GB
263 * range. This method will return at most nb values, equally separated from
264 * start to end. The step between values will be at least 1.0, so the number
265 * of values returned can be lower than nb.
2e427755
GB
266 *
267 * The returned time values are in internal time, ie to get trace time, the
268 * time offset needs to be added to those values.
269 *
270 * @param start
271 * The start time of the time range
272 * @param end
273 * End time of the range
274 * @param nb
5c75d7d2 275 * The maximum number of steps in the x axis.
2e427755
GB
276 * @return The time values (converted to double) to match every step.
277 */
5637b809 278 protected static final double[] getXAxis(long start, long end, int nb) {
2e427755 279 long steps = (end - start);
5c75d7d2
GB
280 int nbVals = nb;
281 if (steps < nb) {
282 nbVals = (int) steps;
283 if (nbVals <= 0) {
284 nbVals = 1;
285 }
286 }
287 double step = steps / (double) nbVals;
2e427755 288
5c75d7d2 289 double timestamps[] = new double[nbVals];
2e427755 290 double curTime = 1;
5c75d7d2 291 for (int i = 0; i < nbVals; i++) {
2e427755
GB
292 timestamps[i] = curTime;
293 curTime += step;
294 }
295 return timestamps;
296 }
297
298 /**
299 * Set the values of the x axis. There is only one array of values for the x
300 * axis for all series of a line chart so it needs to be set once here.
301 *
302 * @param xaxis
303 * The values for the x axis. The values must be in internal
304 * time, ie time offset have been subtracted from trace time
305 * values.
306 */
307 protected final void setXAxis(double[] xaxis) {
308 fXValues = xaxis;
309 }
310
311 /**
312 * Update the series data because the time range has changed. The x axis
313 * values for this data update can be computed using the
c85a741e
GB
314 * {@link TmfCommonXLineChartViewer#getXAxis(long, long, int)} method which
315 * will return a list of uniformely separated time values.
2e427755
GB
316 *
317 * Each series values should be set by calling the
318 * {@link TmfCommonXLineChartViewer#setSeries(String, double[])}.
319 *
320 * This method is responsible for calling the
c85a741e
GB
321 * {@link TmfCommonXLineChartViewer#updateDisplay()} when needed for the new
322 * values to be displayed.
2e427755
GB
323 *
324 * @param start
325 * The start time of the range for which the get the data
326 * @param end
327 * The end time of the range
328 * @param nb
329 * The number of 'points' in the chart.
00968516
GB
330 * @param monitor
331 * The progress monitor object
2e427755 332 */
00968516 333 protected abstract void updateData(long start, long end, int nb, IProgressMonitor monitor);
2e427755
GB
334
335 /**
336 * Set the data for a given series of the graph. The series does not need to
337 * be created before calling this, but it needs to have at least as many
338 * values as the x axis.
339 *
340 * If the series does not exist, it will automatically be created at display
341 * time, with the default values.
342 *
343 * @param seriesName
344 * The name of the series for which to set the values
345 * @param seriesValues
346 * The array of values for the series
347 */
348 protected void setSeries(String seriesName, double[] seriesValues) {
50167813
MK
349 if (fXValues.length != seriesValues.length) {
350 throw new IllegalStateException("All series in list must be of length : " + fXValues.length); //$NON-NLS-1$
2e427755
GB
351 }
352 fSeriesValues.put(seriesName, seriesValues);
353 }
354
355 /**
356 * Add a new series to the XY line chart. By default, it is a simple solid
357 * line.
358 *
2e427755
GB
359 * @param seriesName
360 * The name of the series to create
361 * @return The series so that the concrete viewer can modify its properties
362 * if required
363 */
364 protected ILineSeries addSeries(String seriesName) {
c85a741e
GB
365 ISeriesSet seriesSet = getSwtChart().getSeriesSet();
366 int seriesCount = seriesSet.getSeries().length;
367 ILineSeries series = (ILineSeries) seriesSet.createSeries(SeriesType.LINE, seriesName);
2e427755
GB
368 series.setVisible(true);
369 series.enableArea(false);
c85a741e 370 series.setLineStyle(LINE_STYLES[(seriesCount / (LINE_COLORS.length)) % LINE_STYLES.length]);
2e427755 371 series.setSymbolType(PlotSymbolType.NONE);
c85a741e 372 series.setLineColor(Display.getDefault().getSystemColor(LINE_COLORS[seriesCount % LINE_COLORS.length]));
2e427755
GB
373 return series;
374 }
375
376 /**
377 * Delete a series from the chart and its values from the viewer.
378 *
379 * @param seriesName
380 * Name of the series to delete
381 */
382 protected void deleteSeries(String seriesName) {
383 ISeries series = getSwtChart().getSeriesSet().getSeries(seriesName);
384 if (series != null) {
385 getSwtChart().getSeriesSet().deleteSeries(series.getId());
386 }
387 fSeriesValues.remove(seriesName);
388 }
389
390 /**
391 * Update the chart's values before refreshing the viewer
392 */
393 protected void updateDisplay() {
e18d40d0
GB
394 /* Content is not up to date, increment dirtiness */
395 fDirty.incrementAndGet();
2e427755
GB
396 Display.getDefault().asyncExec(new Runnable() {
397 final TmfChartTimeStampFormat tmfChartTimeStampFormat = new TmfChartTimeStampFormat(getTimeOffset());
398
399 @Override
400 public void run() {
e18d40d0
GB
401 try {
402 if (!getSwtChart().isDisposed()) {
403 double[] xValues = fXValues;
404 double maxy = DEFAULT_MAXY;
405 double miny = DEFAULT_MINY;
406 for (Entry<String, double[]> entry : fSeriesValues.entrySet()) {
407 ILineSeries series = (ILineSeries) getSwtChart().getSeriesSet().getSeries(entry.getKey());
408 if (series == null) {
409 series = addSeries(entry.getKey());
410 }
411 series.setXSeries(xValues);
412 /*
413 * Find the minimal and maximum values in this
414 * series
415 */
416 for (double value : entry.getValue()) {
417 maxy = Math.max(maxy, value);
418 miny = Math.min(miny, value);
419 }
420 series.setYSeries(entry.getValue());
2e427755 421 }
e18d40d0
GB
422 if (maxy == DEFAULT_MAXY) {
423 maxy = 1.0;
2e427755 424 }
2e427755 425
e18d40d0
GB
426 IAxisTick xTick = getSwtChart().getAxisSet().getXAxis(0).getTick();
427 xTick.setFormat(tmfChartTimeStampFormat);
2e427755 428
e18d40d0
GB
429 final double start = 0.0;
430 double end = getWindowEndTime() - getWindowStartTime();
431 getSwtChart().getAxisSet().getXAxis(0).setRange(new Range(start, end));
432 if (maxy > miny) {
433 getSwtChart().getAxisSet().getYAxis(0).setRange(new Range(miny, maxy));
434 }
435 getSwtChart().redraw();
436
437 if (isSendTimeAlignSignals()) {
438 // The width of the chart might have changed and its
439 // time axis might be misaligned with the other
440 // views
441 Point viewPos = TmfCommonXLineChartViewer.this.getParent().getParent().toDisplay(0, 0);
442 int axisPos = getSwtChart().toDisplay(0, 0).x + getPointAreaOffset();
443 int timeAxisOffset = axisPos - viewPos.x;
444 TmfTimeViewAlignmentInfo timeAlignmentInfo = new TmfTimeViewAlignmentInfo(getControl().getShell(), viewPos, timeAxisOffset);
445 TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(TmfCommonXLineChartViewer.this, timeAlignmentInfo, true));
446 }
54404589 447 }
e18d40d0
GB
448 } finally {
449 /* Content has been updated, decrement dirtiness */
450 fDirty.decrementAndGet();
2e427755
GB
451 }
452 }
453 });
454 }
455
456 /**
457 * Create the series once the initialization of the viewer's data source is
458 * done. Series do not need to be created before setting their values, but
459 * if their appearance needs to be customized, this method is a good place
460 * to do so. It is called only once per trace.
461 */
462 protected void createSeries() {
463
464 }
465
bf06b33c
MK
466 @Override
467 protected void clearContent() {
468 getSwtChart().getAxisSet().getXAxis(0).getTick().setFormat(null);
469 super.clearContent();
470 }
471
c27e9fec
GB
472 @Override
473 public boolean isDirty() {
e18d40d0
GB
474 /* Check the parent's or this view's own dirtiness */
475 return super.isDirty() || (fDirty.get() != 0);
c27e9fec
GB
476 }
477
2e427755 478}
This page took 0.104159 seconds and 5 git commands to generate.