analysis.lami: Split axis titles in label and units
[deliverable/tracecompass.git] / analysis / org.eclipse.tracecompass.analysis.lami.ui / src / org / eclipse / tracecompass / internal / provisional / analysis / lami / ui / viewers / LamiXYChartViewer.java
index 874e3b5a35b675a7cea993638e75f410e60e1118..15f51518ced01b1929dfe0acccb97aa585828233 100644 (file)
@@ -12,6 +12,7 @@ package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
 import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
 
+import java.math.BigDecimal;
 import java.text.Format;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -43,7 +44,8 @@ import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.L
 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
-import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTimeStampFormat;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.format.LamiDecimalUnitFormat;
+import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.format.LamiTimeStampFormat;
 import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal;
 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
 import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer;
@@ -71,14 +73,10 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
      */
     protected static final String UNKNOWN = "?"; //$NON-NLS-1$
 
-    /** Zero value */
-    protected static final double ZERO = 0.0;
-
-    /** Symbol for seconds (used in the custom ns -> s conversion) */
-    private static final String SECONDS_SYMBOL = "s"; //$NON-NLS-1$
-
-    /** Symbol for nanoseconds (used in the custom ns -> s conversion) */
-    private static final String NANOSECONDS_SYMBOL = "ns"; //$NON-NLS-1$
+    /** Zero long value */
+    protected static final long ZERO_LONG = 0L;
+    /** Zero double value */
+    protected static final double ZERO_DOUBLE = 0.0;
 
     /**
      * Function to use to map Strings read from the data table to doubles for
@@ -86,7 +84,7 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
      */
     protected static final ToDoubleFunction<@Nullable String> DOUBLE_MAPPER = str -> {
         if (str == null || str.equals(UNKNOWN)) {
-            return ZERO;
+            return ZERO_LONG;
         }
         return Double.parseDouble(str);
     };
@@ -143,12 +141,21 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
     /**
      * Decimal formatter to display nanoseconds as seconds.
      */
-    protected static final DecimalUnitFormat NANO_TO_SECS_FORMATTER = new DecimalUnitFormat(0.000000001);
+    protected static final DecimalUnitFormat NANO_TO_SECS_FORMATTER = new LamiDecimalUnitFormat(0.000000001);
 
     /**
      * Default decimal formatter.
      */
-    protected static final DecimalUnitFormat DECIMAL_FORMATTER = new DecimalUnitFormat();
+    protected static final DecimalUnitFormat DECIMAL_FORMATTER = new LamiDecimalUnitFormat();
+
+    /** Symbol for seconds (used in the custom ns -> s conversion) */
+    private static final String SECONDS_SYMBOL = "s"; //$NON-NLS-1$
+
+    /** Symbol for nanoseconds (used in the custom ns -> s conversion) */
+    private static final String NANOSECONDS_SYMBOL = "ns"; //$NON-NLS-1$
+
+    /** Maximum amount of digits that can be represented into a double */
+    private static final int BIG_DECIMAL_DIVISION_SCALE = 22;
 
     private final Listener fResizeListener = event -> {
         /* Refresh the titles to fit the current chart size */
@@ -164,8 +171,12 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
     private final Chart fChart;
 
     private final String fChartTitle;
-    private final String fXTitle;
-    private final String fYTitle;
+
+    private String fXLabel;
+    private @Nullable String fXUnits;
+
+    private String fYLabel;
+    private @Nullable String fYUnits;
 
     private boolean fSelected;
     private Set<Integer> fSelection;
@@ -191,6 +202,9 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
         fChartModel = chartModel;
         fSelection = new HashSet<>();
 
+        fXLabel = ""; //$NON-NLS-1$
+        fYLabel = ""; //$NON-NLS-1$
+
         fChart = new Chart(parent, SWT.NONE);
         fChart.addListener(SWT.Resize, fResizeListener);
 
@@ -201,14 +215,9 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
         if (fChartModel.getXSeriesColumns().size() == 1) {
             /*
              * There is only 1 series in the chart, we will use its name as the
-             * Y axis (and hide the legend).
+             * X axis.
              */
-            String seriesName = getChartModel().getXSeriesColumns().get(0);
-            // The time duration formatter converts ns to s on the axis
-            if (NANOSECONDS_SYMBOL.equals(getXAxisAspects().get(0).getUnits())) {
-                seriesName = getXAxisAspects().get(0).getName() + " (" + SECONDS_SYMBOL + ')'; //$NON-NLS-1$
-            }
-            fXTitle = seriesName;
+            innerSetXTitle(getXAxisAspects().get(0).getName(), getXAxisAspects().get(0).getUnits());
         } else {
             /*
              * There are multiple series in the chart, if they all share the same
@@ -229,19 +238,13 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
                 xBaseTitle = getXAxisAspects().get(0).getName();
             }
 
-            String units = getXAxisAspects().get(0).getUnits();
-            if (nbDiffAspectsUnits == 1 && units != null) {
+            String units = null;
+            if (nbDiffAspectsUnits == 1) {
                 /* All aspects use the same unit type */
-
-                // The time duration formatter converts ns to s on the axis
-                if (NANOSECONDS_SYMBOL.equals(units)) {
-                    units = SECONDS_SYMBOL;
-                }
-                fXTitle = xBaseTitle + " (" + units + ')'; //$NON-NLS-1$
-            } else {
-                /* Various unit types, just say "Value" */
-                fXTitle = nullToEmptyString(xBaseTitle);
+                units = getXAxisAspects().get(0).getUnits();
             }
+
+            innerSetXTitle(xBaseTitle, units);
         }
 
         /* Set Y axis title */
@@ -250,12 +253,9 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
              * There is only 1 series in the chart, we will use its name as the
              * Y axis (and hide the legend).
              */
-            String seriesName = getChartModel().getYSeriesColumns().get(0);
-            // The time duration formatter converts ns to s on the axis
-            if (NANOSECONDS_SYMBOL.equals(getYAxisAspects().get(0).getUnits())) {
-                seriesName = getYAxisAspects().get(0).getName() + " (" + SECONDS_SYMBOL + ')'; //$NON-NLS-1$
-            }
-            fYTitle = seriesName;
+            innerSetYTitle(getYAxisAspects().get(0).getName(), getYAxisAspects().get(0).getUnits());
+
+            /* Hide the legend */
             fChart.getLegend().setVisible(false);
         } else {
             /*
@@ -277,20 +277,14 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
                 yBaseTitle = getYAxisAspects().get(0).getName();
             }
 
-            String units = getYAxisAspects().get(0).getUnits();
-            if (nbDiffAspectsUnits == 1 && units != null) {
+            String units = null;
+            if (nbDiffAspectsUnits == 1) {
                 /* All aspects use the same unit type */
-
-                // The time duration formatter converts ns to s on the axis
-                if (NANOSECONDS_SYMBOL.equals(units)) {
-                    units = SECONDS_SYMBOL;
-                }
-                fYTitle = yBaseTitle + " (" + units + ')'; //$NON-NLS-1$
-            } else {
-                /* Various unit types, don't display any units */
-                fYTitle = nullToEmptyString(yBaseTitle);
+                units = getYAxisAspects().get(0).getUnits();
             }
 
+            innerSetYTitle(yBaseTitle, units);
+
             /* Put legend at the bottom */
             fChart.getLegend().setPosition(SWT.BOTTOM);
         }
@@ -316,6 +310,122 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
         });
     }
 
+    /**
+     * Set the Y axis title and refresh the chart.
+     *
+     * @param label
+     *            the label string.
+     * @param units
+     *            the units string.
+     */
+    protected void setYTitle(@Nullable String label, @Nullable String units) {
+        innerSetYTitle(label, units);
+    }
+
+    private void innerSetYTitle(@Nullable String label, @Nullable String units) {
+        fYLabel = nullToEmptyString(label);
+        innerSetYUnits(units);
+        refreshDisplayTitles();
+    }
+
+    /**
+     * Set the units on the Y Axis title and refresh the chart.
+     *
+     * @param units
+     *            the units string.
+     */
+    protected void setYUnits(@Nullable String units) {
+        innerSetYUnits(units);
+    }
+
+    private void innerSetYUnits(@Nullable String units) {
+        /*
+         * All time durations in the Lami protocol are nanoseconds, on the
+         * charts we use an axis formater that converts back to seconds as a
+         * base unit and then uses prefixes like nano and milli depending on the
+         * range.
+         *
+         * So set the units to seconds in the title to match the base unit of
+         * the formater.
+         */
+        if (NANOSECONDS_SYMBOL.equals(units)) {
+            fYUnits = SECONDS_SYMBOL;
+        } else {
+            fYUnits = units;
+        }
+        refreshDisplayTitles();
+    }
+
+    /**
+     * Get the Y axis title string.
+     *
+     * If the units is non-null, the title will be: "label (units)"
+     *
+     * If the units is null, the title will be: "label"
+     *
+     * @return the title of the Y axis.
+     */
+    protected String getYTitle() {
+        if (fYUnits == null) {
+            return fYLabel;
+        }
+        return fYLabel + " (" + fYUnits + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+    }
+
+    /**
+     * Set the X axis title and refresh the chart.
+     *
+     * @param label
+     *            the label string.
+     * @param units
+     *            the units string.
+     */
+    protected void setXTitle(@Nullable String label, @Nullable String units) {
+        innerSetXTitle(label, units);
+    }
+
+    private void innerSetXTitle(@Nullable String label, @Nullable String units) {
+        fXLabel = nullToEmptyString(label);
+        innerSetXUnits(units);
+        refreshDisplayTitles();
+    }
+
+    /**
+     * Set the units on the X Axis title.
+     *
+     * @param units
+     *            the units string
+     */
+    protected void setXUnits(@Nullable String units) {
+        innerSetXUnits(units);
+    }
+
+    private void innerSetXUnits(@Nullable String units) {
+        /* The time duration formatter converts ns to s on the axis */
+        if (NANOSECONDS_SYMBOL.equals(units)) {
+            fXUnits = SECONDS_SYMBOL;
+        } else {
+            fXUnits = units;
+        }
+        refreshDisplayTitles();
+    }
+
+    /**
+     * Get the X axis title string.
+     *
+     * If the units is non-null, the title will be: "label (units)"
+     *
+     * If the units is null, the title will be: "label"
+     *
+     * @return the title of the Y axis.
+     */
+    protected String getXTitle() {
+        if (fXUnits == null) {
+            return fXLabel;
+        }
+        return fXLabel + " (" + fXUnits + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+    }
+
     /**
      * Util method to check if a list of aspects are all continuous.
      *
@@ -360,45 +470,64 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
      *            The list of aspects of the axis.
      * @param entries
      *            The list of entries of the chart.
+     * @param internalRange
+     *            The internal range for value transformation
+     * @param externalRange
+     *            The external range for value transformation
      * @return a formatter for the axis.
      */
-    protected static Format getContinuousAxisFormatter(List<LamiTableEntryAspect> axisAspects, List<LamiTableEntry> entries) {
+    protected static Format getContinuousAxisFormatter(List<LamiTableEntryAspect> axisAspects, List<LamiTableEntry> entries , @Nullable LamiGraphRange internalRange, @Nullable LamiGraphRange externalRange) {
+
+        Format formatter = DECIMAL_FORMATTER;
 
         if (areAspectsTimeStamp(axisAspects)) {
             /* Set a TimeStamp formatter depending on the duration between the first and last value */
-            double max = Double.MIN_VALUE;
-            double min = Double.MAX_VALUE;
+            BigDecimal max = new BigDecimal(Long.MIN_VALUE);
+            BigDecimal min = new BigDecimal(Long.MAX_VALUE);
 
             for (LamiTableEntry entry : entries) {
                 for (LamiTableEntryAspect aspect : axisAspects) {
-                    Double current = aspect.resolveDouble(entry);
-                    if (current != null) {
-                        max = Math.max(max, current);
-                        min = Math.min(min, current);
+                    @Nullable Number number = aspect.resolveNumber(entry);
+                    if (number != null) {
+                        BigDecimal current = new BigDecimal(number.toString());
+                        max = current.max(max);
+                        min = current.min(min);
                     }
                 }
             }
-            long duration = (long) max - (long) min;
 
+            long duration = max.subtract(min).longValue();
             if (duration > TimeUnit.DAYS.toNanos(1)) {
-                return DAYS_FORMATTER;
+                formatter = DAYS_FORMATTER;
             } else if (duration > TimeUnit.HOURS.toNanos(1)) {
-                return HOURS_FORMATTER;
+                formatter = HOURS_FORMATTER;
             } else if (duration > TimeUnit.MINUTES.toNanos(1)) {
-                return MINUTES_FORMATTER;
+                formatter = MINUTES_FORMATTER;
             } else if (duration > TimeUnit.SECONDS.toNanos(15)) {
-                return SECONDS_FORMATTER;
+                formatter = SECONDS_FORMATTER;
             } else {
-                return MILLISECONDS_FORMATTER;
+                formatter = MILLISECONDS_FORMATTER;
             }
+            ((LamiTimeStampFormat) formatter).setInternalRange(internalRange);
+            ((LamiTimeStampFormat) formatter).setExternalRange(externalRange);
+
         } else if (areAspectsTimeDuration(axisAspects)) {
-            /* Set the time duration formatter */
-            return NANO_TO_SECS_FORMATTER;
+            /* Set the time duration formatter. */
+            formatter = NANO_TO_SECS_FORMATTER;
+            ((LamiDecimalUnitFormat) formatter).setInternalRange(internalRange);
+            ((LamiDecimalUnitFormat) formatter).setExternalRange(externalRange);
 
         } else {
-            /* For other numeric aspects, use the default decimal unit formatter */
-            return DECIMAL_FORMATTER;
+            /*
+             * For other numeric aspects, use the default lami decimal unit
+             * formatter.
+             */
+            formatter = DECIMAL_FORMATTER;
+            ((LamiDecimalUnitFormat) formatter).setInternalRange(internalRange);
+            ((LamiDecimalUnitFormat) formatter).setExternalRange(externalRange);
         }
+
+        return formatter;
     }
 
     /**
@@ -577,10 +706,10 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
         refreshDisplayTitle(chartTitle, fChartTitle, chartRect.width);
 
         ITitle xTitle = checkNotNull(fChart.getAxisSet().getXAxis(0).getTitle());
-        refreshDisplayTitle(xTitle, fXTitle, plotRect.width);
+        refreshDisplayTitle(xTitle, getXTitle(), plotRect.width);
 
         ITitle yTitle = checkNotNull(fChart.getAxisSet().getYAxis(0).getTitle());
-        refreshDisplayTitle(yTitle, fYTitle, plotRect.height);
+        refreshDisplayTitle(yTitle, getYTitle(), plotRect.height);
     }
 
     /**
@@ -715,4 +844,81 @@ public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer
 
         return toolBar;
     }
+
+    /**
+     * Get a {@link LamiGraphRange} that covers all data points in the result
+     * table.
+     * <p>
+     * The returned range will be the minimum and maximum of the resolved values
+     * of the passed aspects for all result entries. If <code>clampToZero</code>
+     * is true, a positive minimum value will be clamped down to zero.
+     *
+     * @param aspects
+     *            The aspects that the range will represent
+     * @param clampToZero
+     *            If true, a positive minimum value will be clamped down to zero
+     * @return the range
+     */
+    protected LamiGraphRange getRange(List<LamiTableEntryAspect> aspects, boolean clampToZero) {
+        /* Find the minimum and maximum values */
+        BigDecimal min = new BigDecimal(Long.MAX_VALUE);
+        BigDecimal max = new BigDecimal(Long.MIN_VALUE);
+        for (LamiTableEntryAspect lamiTableEntryAspect : aspects) {
+            for (LamiTableEntry entry : getResultTable().getEntries()) {
+                @Nullable Number number = lamiTableEntryAspect.resolveNumber(entry);
+                if (number != null) {
+                    BigDecimal current = new BigDecimal(number.toString());
+                    min = current.min(min);
+                    max = current.max(max);
+                }
+            }
+        }
+
+        if (clampToZero) {
+            min.min(BigDecimal.ZERO);
+        }
+
+        /* Do not allow a range with a zero delta default to 1 */
+        if (max.equals(min)) {
+            max = min.add(BigDecimal.ONE);
+        }
+
+        return new LamiGraphRange(checkNotNull(min), checkNotNull(max));
+    }
+
+    /**
+     * Transform an external value into an internal value. Since SWTChart only
+     * support Double and Lami can pass Long values, loss of precision might
+     * happen. To minimize this, transform the raw values to an internal
+     * representation based on a linear transformation.
+     *
+     * The internal value =
+     *
+     * ((rawValue - rawMinimum) * (internalRangeDelta/rawRangeDelta)) +
+     * internalMinimum
+     *
+     * @param number
+     *            The number to transform
+     * @param internalRange
+     *            The internal range definition to be used
+     * @param externalRange
+     *            The external range definition to be used
+     * @return the transformed value in Double comprised inside the internal
+     *         range
+     */
+    protected static double getInternalDoubleValue(Number number, LamiGraphRange internalRange, LamiGraphRange externalRange) {
+        BigDecimal value = new BigDecimal(number.toString());
+
+        if (externalRange.getDelta().compareTo(BigDecimal.ZERO) == 0) {
+            return internalRange.getMinimum().doubleValue();
+        }
+
+        BigDecimal internalValue = value
+                .subtract(externalRange.getMinimum())
+                .multiply(internalRange.getDelta())
+                .divide(externalRange.getDelta(), BIG_DECIMAL_DIVISION_SCALE, BigDecimal.ROUND_DOWN)
+                .add(internalRange.getMinimum());
+
+        return internalValue.doubleValue();
+    }
 }
This page took 0.031302 seconds and 5 git commands to generate.