1 /*******************************************************************************
2 * Copyright (c) 2014, 2015 École Polytechnique de Montréal and others.
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
10 * Geneviève Bastien - Initial API and implementation
11 *******************************************************************************/
13 package org
.eclipse
.tracecompass
.tmf
.ui
.viewers
.xycharts
.linecharts
;
15 import java
.util
.LinkedHashMap
;
17 import java
.util
.Map
.Entry
;
18 import java
.util
.concurrent
.atomic
.AtomicInteger
;
19 import java
.util
.logging
.Logger
;
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
;
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.
50 * @author - Geneviève Bastien
52 public abstract class TmfCommonXLineChartViewer
extends TmfXYChartViewer
{
54 private static final double DEFAULT_MAXY
= Double
.MIN_VALUE
;
55 private static final double DEFAULT_MINY
= Double
.MAX_VALUE
;
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$
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
};
70 private final Map
<String
, double[]> fSeriesValues
= new LinkedHashMap
<>();
71 private double[] fXValues
;
72 private double fResolution
;
74 private UpdateThread fUpdateThread
;
76 private final AtomicInteger fDirty
= new AtomicInteger();
82 * The parent composite
84 * The title of the viewer
86 * The label of the xAxis
88 * The label of the yAXIS
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));
100 * Set the number of requests per pixel that should be done on this chart
103 * The number of points per pixels
105 protected void setResolution(double resolution
) {
106 fResolution
= resolution
;
110 public void loadTrace(ITmfTrace trace
) {
111 super.loadTrace(trace
);
116 * Formats a log message for this class
119 * The event to log, that will be appended to the class name to
120 * make the full event name
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
125 * @return The complete log message for this class
127 private String
getLogMessage(String event
, @Nullable String parameters
) {
128 if (parameters
== null) {
129 return String
.format(LOG_STRING
, event
, getClass().getName());
131 return String
.format(LOG_STRING_WITH_PARAM
, event
, getClass().getName(), parameters
);
135 * Forces a reinitialization of the data sources, even if it has already
136 * been initialized for this trace before
138 protected void reinitialize() {
139 fSeriesValues
.clear();
140 /* Initializing data: the content is not current */
141 fDirty
.incrementAndGet();
142 Thread thread
= new Thread() {
143 // Don't use TmfUiRefreshHandler (bug 467751)
146 LOGGER
.info(() -> getLogMessage("InitializeThreadStart", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
147 initializeDataSource();
148 if (!getSwtChart().isDisposed()) {
149 getDisplay().asyncExec(new Runnable() {
152 if (!getSwtChart().isDisposed()) {
153 /* Delete the old series */
158 /* View is cleared, decrement fDirty */
159 fDirty
.decrementAndGet();
165 LOGGER
.info(() -> getLogMessage("InitializeThreadEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
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
176 protected void initializeDataSource() {
180 private class UpdateThread
extends Thread
{
181 private final IProgressMonitor fMonitor
;
182 private final int fNumRequests
;
184 public UpdateThread(int numRequests
) {
185 super("Line chart update"); //$NON-NLS-1$
186 fNumRequests
= numRequests
;
187 fMonitor
= new NullProgressMonitor();
192 LOGGER
.info(() -> getLogMessage("UpdateThreadStart", "numRequests=" + fNumRequests
+ ", tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
193 Display
.getDefault().syncExec(new Runnable() {
196 LOGGER
.info(() -> getLogMessage("UpdateDataStart", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
198 updateData(getWindowStartTime(), getWindowEndTime(), fNumRequests
, fMonitor
);
201 * fDirty should have been incremented before creating
202 * the thread, so we decrement it once it is finished
204 fDirty
.decrementAndGet();
205 LOGGER
.info(() -> getLogMessage("UpdateDataEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
209 updateThreadFinished(this);
210 LOGGER
.info(() -> getLogMessage("UpdateThreadEnd", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
213 public void cancel() {
214 LOGGER
.info(() -> getLogMessage("UpdateThreadCanceled", "tid=" + getId())); //$NON-NLS-1$ //$NON-NLS-2$
215 fMonitor
.setCanceled(true);
219 private synchronized void newUpdateThread() {
221 if (!getSwtChart().isDisposed()) {
222 final int numRequests
= (int) (getSwtChart().getPlotArea().getBounds().width
* fResolution
);
223 fUpdateThread
= new UpdateThread(numRequests
);
224 fUpdateThread
.start();
228 private synchronized void updateThreadFinished(UpdateThread thread
) {
229 if (thread
== fUpdateThread
) {
230 fUpdateThread
= null;
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}
240 protected synchronized void cancelUpdate() {
241 if (fUpdateThread
!= null) {
242 fUpdateThread
.cancel();
247 protected void updateContent() {
249 * Content is not up to date, so we increment fDirty. It will be
250 * decremented at the end of the update thread
252 fDirty
.incrementAndGet();
253 getDisplay().asyncExec(new Runnable() {
262 * Convenience method to compute the values of the X axis for a given time
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.
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.
271 * The start time of the time range
273 * End time of the range
275 * The maximum number of steps in the x axis.
276 * @return The time values (converted to double) to match every step.
278 protected static final double[] getXAxis(long start
, long end
, int nb
) {
279 long steps
= (end
- start
);
282 nbVals
= (int) steps
;
287 double step
= steps
/ (double) nbVals
;
289 double timestamps
[] = new double[nbVals
];
291 for (int i
= 0; i
< nbVals
; i
++) {
292 timestamps
[i
] = curTime
;
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.
303 * The values for the x axis. The values must be in internal
304 * time, ie time offset have been subtracted from trace time
307 protected final void setXAxis(double[] xaxis
) {
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
314 * {@link TmfCommonXLineChartViewer#getXAxis(long, long, int)} method which
315 * will return a list of uniformely separated time values.
317 * Each series values should be set by calling the
318 * {@link TmfCommonXLineChartViewer#setSeries(String, double[])}.
320 * This method is responsible for calling the
321 * {@link TmfCommonXLineChartViewer#updateDisplay()} when needed for the new
322 * values to be displayed.
325 * The start time of the range for which the get the data
327 * The end time of the range
329 * The number of 'points' in the chart.
331 * The progress monitor object
333 protected abstract void updateData(long start
, long end
, int nb
, IProgressMonitor monitor
);
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.
340 * If the series does not exist, it will automatically be created at display
341 * time, with the default values.
344 * The name of the series for which to set the values
345 * @param seriesValues
346 * The array of values for the series
348 protected void setSeries(String seriesName
, double[] seriesValues
) {
349 if (fXValues
.length
!= seriesValues
.length
) {
350 throw new IllegalStateException("All series in list must be of length : " + fXValues
.length
); //$NON-NLS-1$
352 fSeriesValues
.put(seriesName
, seriesValues
);
356 * Add a new series to the XY line chart. By default, it is a simple solid
360 * The name of the series to create
361 * @return The series so that the concrete viewer can modify its properties
364 protected ILineSeries
addSeries(String seriesName
) {
365 ISeriesSet seriesSet
= getSwtChart().getSeriesSet();
366 int seriesCount
= seriesSet
.getSeries().length
;
367 ILineSeries series
= (ILineSeries
) seriesSet
.createSeries(SeriesType
.LINE
, seriesName
);
368 series
.setVisible(true);
369 series
.enableArea(false);
370 series
.setLineStyle(LINE_STYLES
[(seriesCount
/ (LINE_COLORS
.length
)) % LINE_STYLES
.length
]);
371 series
.setSymbolType(PlotSymbolType
.NONE
);
372 series
.setLineColor(Display
.getDefault().getSystemColor(LINE_COLORS
[seriesCount
% LINE_COLORS
.length
]));
377 * Delete a series from the chart and its values from the viewer.
380 * Name of the series to delete
382 protected void deleteSeries(String seriesName
) {
383 ISeries series
= getSwtChart().getSeriesSet().getSeries(seriesName
);
384 if (series
!= null) {
385 getSwtChart().getSeriesSet().deleteSeries(series
.getId());
387 fSeriesValues
.remove(seriesName
);
391 * Update the chart's values before refreshing the viewer
393 protected void updateDisplay() {
394 /* Content is not up to date, increment dirtiness */
395 fDirty
.incrementAndGet();
396 Display
.getDefault().asyncExec(new Runnable() {
397 final TmfChartTimeStampFormat tmfChartTimeStampFormat
= new TmfChartTimeStampFormat(getTimeOffset());
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());
411 series
.setXSeries(xValues
);
413 * Find the minimal and maximum values in this
416 for (double value
: entry
.getValue()) {
417 maxy
= Math
.max(maxy
, value
);
418 miny
= Math
.min(miny
, value
);
420 series
.setYSeries(entry
.getValue());
422 if (maxy
== DEFAULT_MAXY
) {
426 IAxisTick xTick
= getSwtChart().getAxisSet().getXAxis(0).getTick();
427 xTick
.setFormat(tmfChartTimeStampFormat
);
429 final double start
= 0.0;
430 double end
= getWindowEndTime() - getWindowStartTime();
431 getSwtChart().getAxisSet().getXAxis(0).setRange(new Range(start
, end
));
433 getSwtChart().getAxisSet().getYAxis(0).setRange(new Range(miny
, maxy
));
435 getSwtChart().redraw();
437 if (isSendTimeAlignSignals()) {
438 // The width of the chart might have changed and its
439 // time axis might be misaligned with the other
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));
449 /* Content has been updated, decrement dirtiness */
450 fDirty
.decrementAndGet();
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.
462 protected void createSeries() {
467 protected void clearContent() {
468 getSwtChart().getAxisSet().getXAxis(0).getTick().setFormat(null);
469 super.clearContent();
473 public boolean isDirty() {
474 /* Check the parent's or this view's own dirtiness */
475 return super.isDirty() || (fDirty
.get() != 0);