| /********************************************************************** |
| * Copyright (c) 2017 Ericsson, Draeger, Auriga |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| **********************************************************************/ |
| |
| package org.eclipse.tracecompass.tmf.ui.viewers.xycharts.linecharts; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.jface.resource.ColorRegistry; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.RGBA; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.tracecompass.common.core.log.TraceCompassLog; |
| import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils; |
| import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLog; |
| import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLogBuilder; |
| import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TmfFilterAppliedSignal; |
| import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TraceCompassFilter; |
| import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils; |
| import org.eclipse.tracecompass.internal.tmf.core.model.filters.TimeQueryRegexFilter; |
| import org.eclipse.tracecompass.internal.tmf.ui.Activator; |
| import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils; |
| import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter; |
| import org.eclipse.tracecompass.tmf.core.model.timegraph.IFilterProperty; |
| import org.eclipse.tracecompass.tmf.core.model.xy.ISeriesModel; |
| import org.eclipse.tracecompass.tmf.core.model.xy.ITmfCommonXAxisModel; |
| import org.eclipse.tracecompass.tmf.core.model.xy.ITmfXYDataProvider; |
| import org.eclipse.tracecompass.tmf.core.model.xy.ITmfXyModel; |
| import org.eclipse.tracecompass.tmf.core.presentation.IXYPresentationProvider; |
| import org.eclipse.tracecompass.tmf.core.presentation.IYAppearance; |
| import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor; |
| import org.eclipse.tracecompass.tmf.core.presentation.XYPresentationProvider; |
| import org.eclipse.tracecompass.tmf.core.response.ITmfResponse; |
| import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal; |
| import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; |
| import org.eclipse.tracecompass.tmf.ui.colors.RGBAUtil; |
| import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo; |
| import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal; |
| import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.AxisRange; |
| import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.TmfChartTimeStampFormat; |
| import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.TmfXYChartViewer; |
| import org.swtchart.IAxisSet; |
| import org.swtchart.IAxisTick; |
| import org.swtchart.IBarSeries; |
| import org.swtchart.ILineSeries; |
| import org.swtchart.ISeries; |
| import org.swtchart.ISeries.SeriesType; |
| import org.swtchart.ISeriesSet; |
| import org.swtchart.LineStyle; |
| import org.swtchart.Range; |
| |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableMap.Builder; |
| import com.google.common.collect.Multimap; |
| |
| /** |
| * XY Chart viewer class implementation. All series in this viewer use the same |
| * X axis values. They are automatically created as values are provided for a |
| * key. Series by default will be displayed as a line. Each series appearance |
| * can be overridden when creating it. |
| * |
| * @author Yonni Chen |
| * @since 3.2 |
| */ |
| public abstract class TmfCommonXAxisChartViewer extends TmfXYChartViewer { |
| |
| private static final String DIRTY_UNDERFLOW_ERROR = "Dirty underflow error"; //$NON-NLS-1$ |
| |
| private static final Map<String, ILineSeries.PlotSymbolType> SYMBOL_MAP; |
| private static final ColorRegistry COLOR_REGISTRY = new ColorRegistry(); |
| |
| static { |
| ImmutableMap.Builder<String, ILineSeries.PlotSymbolType> builder = new Builder<>(); |
| builder.put(IYAppearance.SymbolStyle.NONE, ILineSeries.PlotSymbolType.NONE); |
| builder.put(IYAppearance.SymbolStyle.CIRCLE, ILineSeries.PlotSymbolType.CIRCLE); |
| builder.put(IYAppearance.SymbolStyle.CROSS, ILineSeries.PlotSymbolType.CROSS); |
| builder.put(IYAppearance.SymbolStyle.DIAMOND, ILineSeries.PlotSymbolType.DIAMOND); |
| builder.put(IYAppearance.SymbolStyle.INVERTED_TRIANGLE, ILineSeries.PlotSymbolType.INVERTED_TRIANGLE); |
| builder.put(IYAppearance.SymbolStyle.TRIANGLE, ILineSeries.PlotSymbolType.TRIANGLE); |
| builder.put(IYAppearance.SymbolStyle.PLUS, ILineSeries.PlotSymbolType.PLUS); |
| builder.put(IYAppearance.SymbolStyle.SQUARE, ILineSeries.PlotSymbolType.SQUARE); |
| SYMBOL_MAP = builder.build(); |
| } |
| private static final double DEFAULT_MAXY = Double.MIN_VALUE; |
| private static final double DEFAULT_MINY = Double.MAX_VALUE; |
| |
| /** Timeout between updates in the updateData thread **/ |
| private static final long BUILD_UPDATE_TIMEOUT = 500; |
| |
| private static final @NonNull Logger LOGGER = TraceCompassLog.getLogger(TmfCommonXAxisChartViewer.class); |
| private static final int DEFAULT_SERIES_WIDTH = 1; |
| private static final String DIMMED_SERIES_SUFFIX = ".dimmed"; //$NON-NLS-1$ |
| |
| private final double fResolution; |
| private final AtomicInteger fDirty = new AtomicInteger(); |
| private final Map<ITmfTrace, IXYPresentationProvider> fXYPresentationProvider; |
| private UpdateThread fUpdateThread; |
| |
| /** Used for testing **/ |
| private int fOverrideNbPoints = 0; |
| |
| /** |
| * Constructor |
| * |
| * @param parent |
| * The parent composite |
| * @param settings |
| * See {@link TmfXYChartSettings} to know what it contains |
| */ |
| public TmfCommonXAxisChartViewer(Composite parent, TmfXYChartSettings settings) { |
| super(parent, settings.getTitle(), settings.getXLabel(), settings.getYLabel()); |
| getSwtChart().getTitle().setVisible(false); |
| getSwtChart().getLegend().setPosition(SWT.BOTTOM); |
| getSwtChart().getAxisSet().getXAxes()[0].getTitle().setVisible(false); |
| fResolution = settings.getResolution(); |
| setTooltipProvider(new TmfCommonXLineChartTooltipProvider(this)); |
| fXYPresentationProvider = new HashMap<>(); |
| } |
| |
| @Override |
| public void loadTrace(ITmfTrace trace) { |
| super.loadTrace(trace); |
| fXYPresentationProvider.putIfAbsent(trace, new XYPresentationProvider()); |
| } |
| |
| @Override |
| public boolean isDirty() { |
| /* Check the parent's or this view's own dirtiness */ |
| return super.isDirty() || (fDirty.get() != 0); |
| } |
| |
| /** |
| * Force the number of points to a fixed value |
| * |
| * @param nbPoints |
| * The number of points to display, cannot be negative. 0 |
| * means use native resolution. any positive integer means |
| * that number of points |
| */ |
| public synchronized void setNbPoints(int nbPoints) { |
| if (nbPoints < 0) { |
| throw new IllegalArgumentException("Number of points cannot be negative"); //$NON-NLS-1$ |
| } |
| fOverrideNbPoints = nbPoints; |
| updateContent(); |
| } |
| |
| /** |
| * Initialize the data provider of this viewer |
| * |
| * FIXME Make this abstract when incrementing the major version |
| * |
| * @param trace |
| * The trace |
| * @return the data provider |
| * @since 4.0 |
| */ |
| protected ITmfXYDataProvider initializeDataProvider(@NonNull ITmfTrace trace) { |
| throw new UnsupportedOperationException("This needs to be implemented concrete classes"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Gets the presentation provider |
| * |
| * @return The presentation provider |
| * @since 4.0 |
| */ |
| protected IXYPresentationProvider getPresentationProvider() { |
| return Objects.requireNonNull(fXYPresentationProvider.get(getTrace())); |
| } |
| |
| /** |
| * Create an instance of {@link TimeQueryFilter} that will be used by updateData |
| * method. If a viewer need a more specialized instance of |
| * {@link TimeQueryFilter}, it's its responsibility to override this method and |
| * provide the desired instance. |
| * |
| * @param start |
| * The starting value |
| * @param end |
| * The ending value |
| * @param nb |
| * The number of entries |
| * @return An {@link TimeQueryFilter} instance that data provider will use to |
| * extract a model |
| * @since 4.0 |
| * @deprecated Use createQueryParameters instead |
| */ |
| @Deprecated |
| protected @NonNull TimeQueryFilter createQueryFilter(long start, long end, int nb) { |
| return new TimeQueryRegexFilter(getWindowStartTime(), getWindowEndTime(), nb, getRegexes()); |
| } |
| |
| /** |
| * Create map of parameters that will be used by updateData method. If a |
| * viewer need a more specialized map than just the time requested it's its |
| * responsibility to override this method and provide the desired instance. |
| * |
| * @param start |
| * The starting value |
| * @param end |
| * The ending value |
| * @param nb |
| * The number of entries |
| * @return Map of parameters |
| * @since 5.0 |
| */ |
| protected @NonNull Map<String, Object> createQueryParameters(long start, long end, int nb) { |
| Map<@NonNull String, @NonNull Object> parameters = FetchParametersUtils.timeQueryToMap(new TimeQueryFilter(start, end, nb)); |
| Multimap<@NonNull Integer, @NonNull String> regexesMap = getRegexes(); |
| if (!regexesMap.isEmpty()) { |
| parameters.put(DataProviderParameterUtils.REGEX_MAP_FILTERS_KEY, regexesMap.asMap()); |
| } |
| return parameters; |
| } |
| |
| /** |
| * Gets the appearance of a given series. If appearance doesn't exist, a new one |
| * will be created by the presentation provider |
| * |
| * @param seriesName |
| * The name of the series |
| * @return An {@link IYAppearance} instance for the series |
| * @since 4.0 |
| */ |
| public @NonNull IYAppearance getSeriesAppearance(@NonNull String seriesName) { |
| return getPresentationProvider().getAppearance(seriesName, IYAppearance.Type.LINE, DEFAULT_SERIES_WIDTH); |
| } |
| |
| /** |
| * Cancels the currently running update thread. It is automatically called when |
| * the content is updated, but child viewers may want to call it manually to do |
| * some operations before calling |
| * {@link TmfCommonXAxisChartViewer#updateContent} |
| */ |
| protected synchronized void cancelUpdate() { |
| if (fUpdateThread != null) { |
| fUpdateThread.cancel(); |
| } |
| } |
| |
| @Override |
| protected void updateContent() { |
| ITmfTrace trace = getTrace(); |
| if (trace == null) { |
| return; |
| } |
| cancelUpdate(); |
| try (FlowScopeLog parentScope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "CommonXLineChart:ContentUpdateRequested").setCategory(getViewerId()).build()) { //$NON-NLS-1$ |
| /* |
| * Content is not up to date, so we increment fDirty. It will be decremented at |
| * the end of the update thread |
| */ |
| fDirty.incrementAndGet(); |
| getDisplay().asyncExec(() -> { |
| if (!trace.equals(getTrace())) { |
| return; |
| } |
| try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "CommonXLineChart:CreatingUpdateThread").setParentScope(parentScope).build()) { //$NON-NLS-1$ |
| newUpdateThread(trace, scope); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| protected void clearContent() { |
| getSwtChart().getAxisSet().getXAxis(0).getTick().setFormat(null); |
| super.clearContent(); |
| } |
| |
| private @NonNull String getViewerId() { |
| return getClass().getName(); |
| } |
| |
| private class UpdateThread extends Thread { |
| |
| private final ITmfTrace fTrace; |
| private final IProgressMonitor fMonitor; |
| private final int fNumRequests; |
| private final @NonNull FlowScopeLog fScope; |
| |
| public UpdateThread(ITmfTrace trace, int numRequests, @NonNull FlowScopeLog log) { |
| super("Line chart update"); //$NON-NLS-1$ |
| fTrace = trace; |
| fNumRequests = numRequests; |
| fMonitor = new NullProgressMonitor(); |
| fScope = log; |
| } |
| |
| @Override |
| public void run() { |
| try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "CommonXLineChart:UpdateThread", "numRequests=", fNumRequests).setParentScope(fScope).build()) { //$NON-NLS-1$ //$NON-NLS-2$ |
| ITmfXYDataProvider dataProvider = null; |
| try (FlowScopeLog scopeDp = new FlowScopeLogBuilder(LOGGER, Level.FINE, "CommonXLineChart:InitializeDataProvider").setParentScope(fScope).build()) { //$NON-NLS-1$ |
| dataProvider = initializeDataProvider(fTrace); |
| } |
| if (dataProvider == null) { |
| TraceCompassLogUtils.traceInstant(LOGGER, Level.WARNING, "Data provider for this viewer is not available"); //$NON-NLS-1$ |
| return; |
| } |
| try { |
| int numRequests = fNumRequests; |
| if (numRequests == 0) { |
| return; |
| } |
| Map<String, Object> parameters = createQueryParameters(getWindowStartTime(), getWindowEndTime(), numRequests); |
| updateData(dataProvider, parameters, fMonitor); |
| } finally { |
| /* |
| * fDirty should have been incremented before creating the thread, so we |
| * decrement it once it is finished |
| */ |
| if (fDirty.decrementAndGet() < 0) { |
| Activator.getDefault().logError(DIRTY_UNDERFLOW_ERROR, new Throwable()); |
| } |
| } |
| updateThreadFinished(this); |
| } |
| } |
| |
| public void cancel() { |
| TraceCompassLogUtils.traceInstant(LOGGER, Level.FINE, "CommonXLineChart:UpdateThreadCanceled"); //$NON-NLS-1$ |
| fMonitor.setCanceled(true); |
| } |
| |
| /** |
| * This method is responsible for calling the |
| * {@link UpdateThread#updateDisplay(ITmfCommonXAxisModel)} when needed for the |
| * new values to be displayed. |
| * |
| * @param dataProvider |
| * A data provider |
| * @param parameters |
| * A query filter |
| * @param monitor |
| * A monitor for canceling task |
| */ |
| private void updateData(@NonNull ITmfXYDataProvider dataProvider, @NonNull Map<String, Object> parameters, IProgressMonitor monitor) { |
| boolean isComplete = false; |
| do { |
| TmfModelResponse<ITmfXyModel> response = dataProvider.fetchXY(parameters, monitor); |
| ITmfXyModel model = response.getModel(); |
| if (model != null) { |
| updateDisplay(model, monitor); |
| } |
| |
| ITmfResponse.Status status = response.getStatus(); |
| if (status == ITmfResponse.Status.COMPLETED) { |
| /* Model is complete, no need to request again the data provider */ |
| isComplete = true; |
| } else if (status == ITmfResponse.Status.FAILED || status == ITmfResponse.Status.CANCELLED) { |
| /* Error occurred, log and return */ |
| TraceCompassLogUtils.traceInstant(LOGGER, Level.WARNING, response.getStatusMessage()); |
| isComplete = true; |
| } else { |
| /** |
| * Status is RUNNING. Sleeping current thread to wait before request data |
| * provider again |
| **/ |
| try { |
| Thread.sleep(BUILD_UPDATE_TIMEOUT); |
| } catch (InterruptedException e) { |
| /** |
| * InterruptedException is throw by Thread.Sleep and we should retry querying |
| * the data provider |
| **/ |
| TraceCompassLogUtils.traceInstant(LOGGER, Level.INFO, e.getMessage()); |
| Thread.currentThread().interrupt(); |
| } |
| } |
| } while (!isComplete); |
| } |
| |
| /** |
| * Update the chart's values before refreshing the viewer |
| */ |
| private void updateDisplay(ITmfXyModel model, IProgressMonitor monitor) { |
| try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "TmfCommonXAxisChart:UpdateDisplayRequested").setCategory(getViewerId()).build()) { //$NON-NLS-1$ |
| /* Content is not up to date, increment dirtiness */ |
| final ITmfXyModel seriesValues = model; |
| fDirty.incrementAndGet(); |
| |
| Display.getDefault().asyncExec(() -> { |
| final TmfChartTimeStampFormat tmfChartTimeStampFormat = new TmfChartTimeStampFormat(getTimeOffset()); |
| try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "TmfCommonXAxisChart:UpdateDisplay").setParentScope(scope).build()) { //$NON-NLS-1$ |
| if (!fTrace.equals(getTrace())) { |
| return; |
| } |
| if (getSwtChart().isDisposed()) { |
| return; |
| } |
| if (monitor != null && monitor.isCanceled()) { |
| return; |
| } |
| double maxy = DEFAULT_MAXY; |
| double miny = DEFAULT_MINY; |
| long delta = getWindowEndTime() - getWindowStartTime(); |
| if (delta > 0) { |
| for (ISeriesModel entry : seriesValues.getData().values()) { |
| double[] extractXValuesToDisplay = extractXValuesToDisplay(entry.getXAxis()); |
| List<Double> dimmedX = new ArrayList<>(extractXValuesToDisplay.length); |
| List<Double> dimmedY = new ArrayList<>(extractXValuesToDisplay.length); |
| List<Double> brightX = new ArrayList<>(extractXValuesToDisplay.length); |
| List<Double> brightY = new ArrayList<>(extractXValuesToDisplay.length); |
| |
| int[] propertiesArray = entry.getProperties(); |
| double[] data = entry.getData(); |
| for (int i = 0; i < extractXValuesToDisplay.length; i++) { |
| double value = data[i]; |
| /* |
| * Find the minimal and maximum values in this series |
| */ |
| maxy = Math.max(maxy, value); |
| miny = Math.min(miny, value); |
| int properties = (i < propertiesArray.length) ? propertiesArray[i] : 0; |
| if ((properties & IFilterProperty.EXCLUDE) == 0) { |
| if ((properties & IFilterProperty.DIMMED) == 0) { |
| brightX.add(extractXValuesToDisplay[i]); |
| brightY.add(value); |
| } else { |
| dimmedX.add(extractXValuesToDisplay[i]); |
| dimmedY.add(value); |
| } |
| } |
| } |
| double[] brightXArray = new double[brightX.size()]; |
| double[] brightYArray = new double[brightY.size()]; |
| for (int i = 0; i < brightX.size(); i++) { |
| brightXArray[i] = brightX.get(i); |
| brightYArray[i] = brightY.get(i); |
| } |
| double[] dimmedXArray = new double[dimmedX.size()]; |
| double[] dimmedYArray = new double[dimmedY.size()]; |
| for (int i = 0; i < dimmedX.size(); i++) { |
| dimmedXArray[i] = dimmedX.get(i); |
| dimmedYArray[i] = dimmedY.get(i); |
| } |
| |
| // Create and fill the series |
| ISeriesSet seriesSet = getSwtChart().getSeriesSet(); |
| ISeries series = seriesSet.getSeries(entry.getName()); |
| ISeries dimmedSeries = seriesSet.getSeries(entry.getName() + DIMMED_SERIES_SUFFIX); |
| if (brightX.isEmpty()) { |
| // Remove the base series since there is |
| // nothing to show |
| if (series != null) { |
| seriesSet.deleteSeries(entry.getName()); |
| } |
| } else { |
| if (series == null) { |
| series = createSWTSeriesFromModel(entry); |
| } |
| series.setXSeries(brightXArray); |
| series.setYSeries(brightYArray); |
| } |
| if (dimmedX.isEmpty()) { |
| // Remove the base series since there is |
| // nothing to show |
| if (dimmedSeries != null) { |
| seriesSet.deleteSeries(entry.getName() + DIMMED_SERIES_SUFFIX); |
| } |
| } else { |
| if (dimmedSeries == null) { |
| dimmedSeries = createDimmedSeriesFromModel(entry); |
| } |
| dimmedSeries.setXSeries(dimmedXArray); |
| dimmedSeries.setYSeries(dimmedYArray); |
| } |
| } |
| maxy = maxy == DEFAULT_MAXY ? 1.0 : maxy; |
| } else { |
| clearContent(); |
| delta = 1; |
| } |
| |
| IAxisSet axisSet = getSwtChart().getAxisSet(); |
| IAxisTick xTick = axisSet.getXAxis(0).getTick(); |
| xTick.setFormat(tmfChartTimeStampFormat); |
| final double start = 1.0; |
| axisSet.getXAxis(0).setRange(new Range(start, start + delta)); |
| AxisRange fixedYRange = getFixedYRange(); |
| if (fixedYRange == null) { |
| axisSet.getYAxis(0).adjustRange(); |
| } else { |
| axisSet.getYAxis(0).setRange( |
| new Range(fixedYRange.getLower(), fixedYRange.getUpper())); |
| } |
| getSwtChart().redraw(); |
| |
| if (isSendTimeAlignSignals()) { |
| /* |
| * The width of the chart might have changed and its time axis might be |
| * misaligned with the other views |
| */ |
| Composite parent = TmfCommonXAxisChartViewer.this.getParent(); |
| if (parent == null || parent.getParent() == null) { |
| return; |
| } |
| Point viewPos = parent.getParent().toDisplay(0, 0); |
| int axisPos = getSwtChart().toDisplay(0, 0).x + getPointAreaOffset(); |
| int timeAxisOffset = axisPos - viewPos.x; |
| TmfTimeViewAlignmentInfo timeAlignmentInfo = new TmfTimeViewAlignmentInfo(getControl().getShell(), viewPos, timeAxisOffset); |
| TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(TmfCommonXAxisChartViewer.this, timeAlignmentInfo, true)); |
| } |
| |
| } finally { |
| /* Content has been updated, decrement dirtiness */ |
| if (fDirty.decrementAndGet() < 0) { |
| Activator.getDefault().logError(DIRTY_UNDERFLOW_ERROR, new Throwable()); |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Since the XY Model returned by data provider contains directly the requested |
| * time as long array, we need to convert it to double array for the SWT Chart. |
| */ |
| private double[] extractXValuesToDisplay(long[] xValuesRequested) { |
| double[] xValuesToDisplay = new double[xValuesRequested.length]; |
| long offset = getTimeOffset(); |
| |
| for (int i = 0; i < xValuesRequested.length; ++i) { |
| xValuesToDisplay[i] = (xValuesRequested[i] - offset); |
| } |
| return xValuesToDisplay; |
| } |
| |
| private @NonNull ISeries createSWTSeriesFromModel(ISeriesModel yModel) { |
| ISeriesSet seriesSet = getSwtChart().getSeriesSet(); |
| |
| String seriesName = yModel.getName(); |
| IYAppearance appearance = getSeriesAppearance(seriesName); |
| |
| String type = appearance.getType(); |
| RGBAColor rgb = appearance.getColor(); |
| COLOR_REGISTRY.put(rgb.toString(), RGBAUtil.fromRGBAColor(rgb).rgb); |
| Color color = COLOR_REGISTRY.get(rgb.toString()); |
| String symbolType = appearance.getSymbolStyle(); |
| |
| if (type.equals(IYAppearance.Type.BAR)) { |
| IBarSeries barSeries = (IBarSeries) seriesSet.createSeries(SeriesType.BAR, seriesName); |
| barSeries.enableStack(true); |
| barSeries.setBarColor(color); |
| barSeries.setBarPadding(0); |
| barSeries.setVisible(true); |
| return barSeries; |
| } |
| |
| /** |
| * Default is line chart |
| */ |
| ILineSeries lineSeries = (ILineSeries) seriesSet.createSeries(SeriesType.LINE, seriesName); |
| lineSeries.enableArea(IYAppearance.Type.AREA.equals(type)); |
| lineSeries.setLineStyle(LineStyle.valueOf(appearance.getStyle())); |
| lineSeries.setSymbolType(SYMBOL_MAP.getOrDefault(symbolType, ILineSeries.PlotSymbolType.NONE)); |
| lineSeries.setLineColor(color); |
| lineSeries.setSymbolColor(color); |
| lineSeries.setVisible(true); |
| lineSeries.setLineWidth(appearance.getWidth()); |
| return lineSeries; |
| } |
| |
| private @NonNull ISeries createDimmedSeriesFromModel(ISeriesModel yModel) { |
| ISeriesSet seriesSet = getSwtChart().getSeriesSet(); |
| |
| String seriesName = yModel.getName() + DIMMED_SERIES_SUFFIX; |
| IYAppearance appearance = getSeriesAppearance(yModel.getName()); |
| |
| String type = appearance.getType(); |
| float[] rgb = appearance.getColor().getHSBA(); |
| COLOR_REGISTRY.put(rgb.toString(), new RGBA(rgb[0], rgb[1] * 0.5f, rgb[2] * 0.5f, rgb[3]).rgb); |
| Color color = COLOR_REGISTRY.get(rgb.toString()); |
| String symbolType = appearance.getSymbolStyle(); |
| |
| if (type.equals(IYAppearance.Type.BAR)) { |
| IBarSeries barSeries = (IBarSeries) seriesSet.createSeries(SeriesType.BAR, seriesName); |
| barSeries.enableStack(true); |
| barSeries.setBarColor(color); |
| barSeries.setBarPadding(0); |
| barSeries.setVisible(true); |
| return barSeries; |
| } |
| |
| /** |
| * Default is line chart |
| */ |
| ILineSeries lineSeries = (ILineSeries) seriesSet.createSeries(SeriesType.LINE, seriesName); |
| lineSeries.enableArea(IYAppearance.Type.AREA.equals(type)); |
| lineSeries.setLineStyle(LineStyle.valueOf(appearance.getStyle())); |
| lineSeries.setSymbolType(SYMBOL_MAP.getOrDefault(symbolType, ILineSeries.PlotSymbolType.NONE)); |
| lineSeries.setLineColor(color); |
| lineSeries.setSymbolColor(color); |
| lineSeries.setVisible(true); |
| lineSeries.setLineWidth(appearance.getWidth()); |
| return lineSeries; |
| } |
| |
| private synchronized void updateThreadFinished(UpdateThread thread) { |
| if (thread == fUpdateThread) { |
| fUpdateThread = null; |
| } |
| } |
| } |
| |
| private synchronized void newUpdateThread(@NonNull ITmfTrace trace, @NonNull FlowScopeLog fScope) { |
| if (getSwtChart().isDisposed()) { |
| return; |
| } |
| int numRequests = fOverrideNbPoints != 0 ? fOverrideNbPoints : (int) Math.min(getWindowEndTime() - getWindowStartTime() + 1, (long) (getSwtChart().getPlotArea().getBounds().width * fResolution)); |
| fUpdateThread = new UpdateThread(trace, numRequests, fScope); |
| fUpdateThread.start(); |
| } |
| |
| @TmfSignalHandler |
| @Override |
| public void traceClosed(@Nullable TmfTraceClosedSignal signal) { |
| cancelUpdate(); |
| super.traceClosed(signal); |
| if (signal != null) { |
| fXYPresentationProvider.remove(signal.getTrace()); |
| } |
| } |
| |
| /** |
| * Set or remove the global regex filter value |
| * |
| * @param signal |
| * the signal carrying the regex value |
| * @since 4.2 |
| */ |
| @TmfSignalHandler |
| public void regexFilterApplied(TmfFilterAppliedSignal signal) { |
| updateContent(); |
| } |
| |
| /** |
| * This method build the multimap of regexes by property that will be used to |
| * filter the timegraph states |
| * |
| * Override this method to add other regexes with their properties. The data |
| * provider should handle everything after. |
| * |
| * @return The multimap of regexes by property |
| * @since 4.2 |
| */ |
| protected @NonNull Multimap<@NonNull Integer, @NonNull String> getRegexes() { |
| Multimap<@NonNull Integer, @NonNull String> regexes = HashMultimap.create(); |
| |
| ITmfTrace trace = getTrace(); |
| if (trace == null) { |
| return regexes; |
| } |
| TraceCompassFilter globalFilter = TraceCompassFilter.getFilterForTrace(trace); |
| if (globalFilter == null) { |
| return regexes; |
| } |
| regexes.putAll(IFilterProperty.DIMMED, globalFilter.getRegexes()); |
| |
| return regexes; |
| } |
| } |