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