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