1 /*******************************************************************************
2 * Copyright (c) 2015, 2016 EfficiOS Inc., Michael Jeanson
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 *******************************************************************************/
10 package org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.ui
.viewers
;
12 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
13 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.nullToEmptyString
;
15 import java
.math
.BigDecimal
;
16 import java
.text
.Format
;
17 import java
.util
.ArrayList
;
18 import java
.util
.HashSet
;
19 import java
.util
.List
;
21 import java
.util
.concurrent
.TimeUnit
;
22 import java
.util
.function
.ToDoubleFunction
;
24 import org
.eclipse
.jdt
.annotation
.NonNull
;
25 import org
.eclipse
.jdt
.annotation
.Nullable
;
26 import org
.eclipse
.swt
.SWT
;
27 import org
.eclipse
.swt
.events
.SelectionEvent
;
28 import org
.eclipse
.swt
.events
.SelectionListener
;
29 import org
.eclipse
.swt
.graphics
.Color
;
30 import org
.eclipse
.swt
.graphics
.Font
;
31 import org
.eclipse
.swt
.graphics
.GC
;
32 import org
.eclipse
.swt
.graphics
.Image
;
33 import org
.eclipse
.swt
.graphics
.Point
;
34 import org
.eclipse
.swt
.graphics
.Rectangle
;
35 import org
.eclipse
.swt
.widgets
.Composite
;
36 import org
.eclipse
.swt
.widgets
.Control
;
37 import org
.eclipse
.swt
.widgets
.Display
;
38 import org
.eclipse
.swt
.widgets
.Event
;
39 import org
.eclipse
.swt
.widgets
.Listener
;
40 import org
.eclipse
.swt
.widgets
.ToolBar
;
41 import org
.eclipse
.swt
.widgets
.ToolItem
;
42 import org
.eclipse
.tracecompass
.common
.core
.format
.DecimalUnitFormat
;
43 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.aspect
.LamiTableEntryAspect
;
44 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.module
.LamiChartModel
;
45 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.module
.LamiResultTable
;
46 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.core
.module
.LamiTableEntry
;
47 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.ui
.format
.LamiDecimalUnitFormat
;
48 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.ui
.format
.LamiTimeStampFormat
;
49 import org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.ui
.signals
.LamiSelectionUpdateSignal
;
50 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalHandler
;
51 import org
.eclipse
.tracecompass
.tmf
.ui
.viewers
.TmfViewer
;
52 import org
.eclipse
.ui
.ISharedImages
;
53 import org
.eclipse
.ui
.PlatformUI
;
54 import org
.swtchart
.Chart
;
55 import org
.swtchart
.ITitle
;
57 import com
.google
.common
.collect
.ImmutableList
;
60 * Abstract XYChart Viewer for LAMI views.
62 * @author Michael Jeanson
65 public abstract class LamiXYChartViewer
extends TmfViewer
implements ILamiViewer
{
67 /** Ellipsis character */
68 protected static final String ELLIPSIS
= "…"; //$NON-NLS-1$
71 * String representing unknown values. Can be present even in numerical
74 protected static final String UNKNOWN
= "?"; //$NON-NLS-1$
76 /** Zero long value */
77 protected static final long ZERO_LONG
= 0L;
78 /** Zero double value */
79 protected static final double ZERO_DOUBLE
= 0.0;
82 * Function to use to map Strings read from the data table to doubles for
83 * use in SWTChart series.
85 protected static final ToDoubleFunction
<@Nullable String
> DOUBLE_MAPPER
= str
-> {
86 if (str
== null || str
.equals(UNKNOWN
)) {
89 return Double
.parseDouble(str
);
93 * List of standard colors
95 protected static final List
<@NonNull Color
> COLORS
= ImmutableList
.of(
96 new Color(Display
.getDefault(), 72, 120, 207),
97 new Color(Display
.getDefault(), 106, 204, 101),
98 new Color(Display
.getDefault(), 214, 95, 95),
99 new Color(Display
.getDefault(), 180, 124, 199),
100 new Color(Display
.getDefault(), 196, 173, 102),
101 new Color(Display
.getDefault(), 119, 190, 219)
105 * List of "light" colors (when unselected)
107 protected static final List
<@NonNull Color
> LIGHT_COLORS
= ImmutableList
.of(
108 new Color(Display
.getDefault(), 173, 195, 233),
109 new Color(Display
.getDefault(), 199, 236, 197),
110 new Color(Display
.getDefault(), 240, 196, 196),
111 new Color(Display
.getDefault(), 231, 213, 237),
112 new Color(Display
.getDefault(), 231, 222, 194),
113 new Color(Display
.getDefault(), 220, 238, 246)
117 * Time stamp formatter for intervals in the days range.
119 protected static final LamiTimeStampFormat DAYS_FORMATTER
= new LamiTimeStampFormat("dd HH:mm"); //$NON-NLS-1$
122 * Time stamp formatter for intervals in the hours range.
124 protected static final LamiTimeStampFormat HOURS_FORMATTER
= new LamiTimeStampFormat("HH:mm"); //$NON-NLS-1$
127 * Time stamp formatter for intervals in the minutes range.
129 protected static final LamiTimeStampFormat MINUTES_FORMATTER
= new LamiTimeStampFormat("mm:ss"); //$NON-NLS-1$
132 * Time stamp formatter for intervals in the seconds range.
134 protected static final LamiTimeStampFormat SECONDS_FORMATTER
= new LamiTimeStampFormat("ss"); //$NON-NLS-1$
137 * Time stamp formatter for intervals in the milliseconds range.
139 protected static final LamiTimeStampFormat MILLISECONDS_FORMATTER
= new LamiTimeStampFormat("ss.SSS"); //$NON-NLS-1$
142 * Decimal formatter to display nanoseconds as seconds.
144 protected static final DecimalUnitFormat NANO_TO_SECS_FORMATTER
= new LamiDecimalUnitFormat(0.000000001);
147 * Default decimal formatter.
149 protected static final DecimalUnitFormat DECIMAL_FORMATTER
= new LamiDecimalUnitFormat();
151 /** Symbol for seconds (used in the custom ns -> s conversion) */
152 private static final String SECONDS_SYMBOL
= "s"; //$NON-NLS-1$
154 /** Symbol for nanoseconds (used in the custom ns -> s conversion) */
155 private static final String NANOSECONDS_SYMBOL
= "ns"; //$NON-NLS-1$
157 /** Maximum amount of digits that can be represented into a double */
158 private static final int BIG_DECIMAL_DIVISION_SCALE
= 22;
160 private final Listener fResizeListener
= event
-> {
161 /* Refresh the titles to fit the current chart size */
162 refreshDisplayTitles();
164 /* Refresh the Axis labels to fit the current chart size */
165 refreshDisplayLabels();
168 private final LamiResultTable fResultTable
;
169 private final LamiChartModel fChartModel
;
171 private final Chart fChart
;
173 private final String fChartTitle
;
175 private String fXLabel
;
176 private @Nullable String fXUnits
;
178 private String fYLabel
;
179 private @Nullable String fYUnits
;
181 private boolean fSelected
;
182 private Set
<Integer
> fSelection
;
184 private final ToolBar fToolBar
;
187 * Creates a Viewer instance based on SWTChart.
190 * The parent composite to draw in.
192 * The result table containing the data from which to build the
195 * The information about the chart to build
197 public LamiXYChartViewer(Composite parent
, LamiResultTable resultTable
, LamiChartModel chartModel
) {
201 fResultTable
= resultTable
;
202 fChartModel
= chartModel
;
203 fSelection
= new HashSet
<>();
205 fXLabel
= ""; //$NON-NLS-1$
206 fYLabel
= ""; //$NON-NLS-1$
208 fChart
= new Chart(parent
, SWT
.NONE
);
209 fChart
.addListener(SWT
.Resize
, fResizeListener
);
211 /* Set Chart title */
212 fChartTitle
= fResultTable
.getTableClass().getTableTitle();
214 /* Set X axis title */
215 if (fChartModel
.getXSeriesColumns().size() == 1) {
217 * There is only 1 series in the chart, we will use its name as the
220 innerSetXTitle(getXAxisAspects().get(0).getName(), getXAxisAspects().get(0).getUnits());
223 * There are multiple series in the chart, if they all share the same
224 * units, display that.
226 long nbDiffAspectsUnits
= getXAxisAspects().stream()
227 .map(aspect
-> aspect
.getUnits())
231 long nbDiffAspectName
= getXAxisAspects().stream()
232 .map(aspect
-> aspect
.getName())
236 String xBaseTitle
= Messages
.LamiViewer_DefaultValueName
;
237 if (nbDiffAspectName
== 1) {
238 xBaseTitle
= getXAxisAspects().get(0).getName();
242 if (nbDiffAspectsUnits
== 1) {
243 /* All aspects use the same unit type */
244 units
= getXAxisAspects().get(0).getUnits();
247 innerSetXTitle(xBaseTitle
, units
);
250 /* Set Y axis title */
251 if (fChartModel
.getYSeriesColumns().size() == 1) {
253 * There is only 1 series in the chart, we will use its name as the
254 * Y axis (and hide the legend).
256 innerSetYTitle(getYAxisAspects().get(0).getName(), getYAxisAspects().get(0).getUnits());
258 /* Hide the legend */
259 fChart
.getLegend().setVisible(false);
262 * There are multiple series in the chart, if they all share the same
263 * units, display that.
265 long nbDiffAspectsUnits
= getYAxisAspects().stream()
266 .map(aspect
-> aspect
.getUnits())
270 long nbDiffAspectName
= getYAxisAspects().stream()
271 .map(aspect
-> aspect
.getName())
275 String yBaseTitle
= Messages
.LamiViewer_DefaultValueName
;
276 if (nbDiffAspectName
== 1) {
277 yBaseTitle
= getYAxisAspects().get(0).getName();
281 if (nbDiffAspectsUnits
== 1) {
282 /* All aspects use the same unit type */
283 units
= getYAxisAspects().get(0).getUnits();
286 innerSetYTitle(yBaseTitle
, units
);
288 /* Put legend at the bottom */
289 fChart
.getLegend().setPosition(SWT
.BOTTOM
);
292 /* Set all titles and labels font color to black */
293 fChart
.getTitle().setForeground(Display
.getDefault().getSystemColor(SWT
.COLOR_BLACK
));
294 fChart
.getAxisSet().getXAxis(0).getTitle().setForeground(Display
.getDefault().getSystemColor(SWT
.COLOR_BLACK
));
295 fChart
.getAxisSet().getYAxis(0).getTitle().setForeground(Display
.getDefault().getSystemColor(SWT
.COLOR_BLACK
));
296 fChart
.getAxisSet().getXAxis(0).getTick().setForeground(Display
.getDefault().getSystemColor(SWT
.COLOR_BLACK
));
297 fChart
.getAxisSet().getYAxis(0).getTick().setForeground(Display
.getDefault().getSystemColor(SWT
.COLOR_BLACK
));
299 /* Set X label 90 degrees */
300 fChart
.getAxisSet().getXAxis(0).getTick().setTickLabelAngle(90);
302 /* Refresh the titles to fit the current chart size */
303 refreshDisplayTitles();
305 fToolBar
= createChartToolBar();
307 fChart
.addDisposeListener(e
-> {
308 /* Dispose resources of this class */
309 LamiXYChartViewer
.super.dispose();
314 * Set the Y axis title and refresh the chart.
321 protected void setYTitle(@Nullable String label
, @Nullable String units
) {
322 innerSetYTitle(label
, units
);
325 private void innerSetYTitle(@Nullable String label
, @Nullable String units
) {
326 fYLabel
= nullToEmptyString(label
);
327 innerSetYUnits(units
);
328 refreshDisplayTitles();
332 * Set the units on the Y Axis title and refresh the chart.
337 protected void setYUnits(@Nullable String units
) {
338 innerSetYUnits(units
);
341 private void innerSetYUnits(@Nullable String units
) {
343 * All time durations in the Lami protocol are nanoseconds, on the
344 * charts we use an axis formater that converts back to seconds as a
345 * base unit and then uses prefixes like nano and milli depending on the
348 * So set the units to seconds in the title to match the base unit of
351 if (NANOSECONDS_SYMBOL
.equals(units
)) {
352 fYUnits
= SECONDS_SYMBOL
;
356 refreshDisplayTitles();
360 * Get the Y axis title string.
362 * If the units is non-null, the title will be: "label (units)"
364 * If the units is null, the title will be: "label"
366 * @return the title of the Y axis.
368 protected String
getYTitle() {
369 if (fYUnits
== null) {
372 return fYLabel
+ " (" + fYUnits
+ ")"; //$NON-NLS-1$ //$NON-NLS-2$
376 * Set the X axis title and refresh the chart.
383 protected void setXTitle(@Nullable String label
, @Nullable String units
) {
384 innerSetXTitle(label
, units
);
387 private void innerSetXTitle(@Nullable String label
, @Nullable String units
) {
388 fXLabel
= nullToEmptyString(label
);
389 innerSetXUnits(units
);
390 refreshDisplayTitles();
394 * Set the units on the X Axis title.
399 protected void setXUnits(@Nullable String units
) {
400 innerSetXUnits(units
);
403 private void innerSetXUnits(@Nullable String units
) {
404 /* The time duration formatter converts ns to s on the axis */
405 if (NANOSECONDS_SYMBOL
.equals(units
)) {
406 fXUnits
= SECONDS_SYMBOL
;
410 refreshDisplayTitles();
414 * Get the X axis title string.
416 * If the units is non-null, the title will be: "label (units)"
418 * If the units is null, the title will be: "label"
420 * @return the title of the Y axis.
422 protected String
getXTitle() {
423 if (fXUnits
== null) {
426 return fXLabel
+ " (" + fXUnits
+ ")"; //$NON-NLS-1$ //$NON-NLS-2$
430 * Util method to check if a list of aspects are all continuous.
433 * The list of aspects to check.
434 * @return true is all aspects are continuous, otherwise false.
436 protected static boolean areAspectsContinuous(List
<LamiTableEntryAspect
> axisAspects
) {
437 return axisAspects
.stream().allMatch(aspect
-> aspect
.isContinuous());
441 * Util method to check if a list of aspects are all time stamps.
444 * The list of aspects to check.
445 * @return true is all aspects are time stamps, otherwise false.
447 protected static boolean areAspectsTimeStamp(List
<LamiTableEntryAspect
> axisAspects
) {
448 return axisAspects
.stream().allMatch(aspect
-> aspect
.isTimeStamp());
452 * Util method to check if a list of aspects are all time durations.
455 * The list of aspects to check.
456 * @return true is all aspects are time durations, otherwise false.
458 protected static boolean areAspectsTimeDuration(List
<LamiTableEntryAspect
> axisAspects
) {
459 return axisAspects
.stream().allMatch(aspect
-> aspect
.isTimeDuration());
463 * Util method that will return a formatter based on the aspects linked to an axis
465 * If all aspects are time stamps, return a timestamp formatter tuned to the interval.
466 * If all aspects are time durations, return the nanoseconds to seconds formatter.
467 * Otherwise, return the generic decimal formatter.
470 * The list of aspects of the axis.
472 * The list of entries of the chart.
473 * @param internalRange
474 * The internal range for value transformation
475 * @param externalRange
476 * The external range for value transformation
477 * @return a formatter for the axis.
479 protected static Format
getContinuousAxisFormatter(List
<LamiTableEntryAspect
> axisAspects
, List
<LamiTableEntry
> entries
, @Nullable LamiGraphRange internalRange
, @Nullable LamiGraphRange externalRange
) {
481 Format formatter
= DECIMAL_FORMATTER
;
483 if (areAspectsTimeStamp(axisAspects
)) {
484 /* Set a TimeStamp formatter depending on the duration between the first and last value */
485 BigDecimal max
= new BigDecimal(Long
.MIN_VALUE
);
486 BigDecimal min
= new BigDecimal(Long
.MAX_VALUE
);
488 for (LamiTableEntry entry
: entries
) {
489 for (LamiTableEntryAspect aspect
: axisAspects
) {
490 @Nullable Number number
= aspect
.resolveNumber(entry
);
491 if (number
!= null) {
492 BigDecimal current
= new BigDecimal(number
.toString());
493 max
= current
.max(max
);
494 min
= current
.min(min
);
499 long duration
= max
.subtract(min
).longValue();
500 if (duration
> TimeUnit
.DAYS
.toNanos(1)) {
501 formatter
= DAYS_FORMATTER
;
502 } else if (duration
> TimeUnit
.HOURS
.toNanos(1)) {
503 formatter
= HOURS_FORMATTER
;
504 } else if (duration
> TimeUnit
.MINUTES
.toNanos(1)) {
505 formatter
= MINUTES_FORMATTER
;
506 } else if (duration
> TimeUnit
.SECONDS
.toNanos(15)) {
507 formatter
= SECONDS_FORMATTER
;
509 formatter
= MILLISECONDS_FORMATTER
;
511 ((LamiTimeStampFormat
) formatter
).setInternalRange(internalRange
);
512 ((LamiTimeStampFormat
) formatter
).setExternalRange(externalRange
);
514 } else if (areAspectsTimeDuration(axisAspects
)) {
515 /* Set the time duration formatter. */
516 formatter
= NANO_TO_SECS_FORMATTER
;
517 ((LamiDecimalUnitFormat
) formatter
).setInternalRange(internalRange
);
518 ((LamiDecimalUnitFormat
) formatter
).setExternalRange(externalRange
);
522 * For other numeric aspects, use the default lami decimal unit
525 formatter
= DECIMAL_FORMATTER
;
526 ((LamiDecimalUnitFormat
) formatter
).setInternalRange(internalRange
);
527 ((LamiDecimalUnitFormat
) formatter
).setExternalRange(externalRange
);
534 * Get the chart result table.
536 * @return The chart result table.
538 protected LamiResultTable
getResultTable() {
543 * Get the chart model.
545 * @return The chart model.
547 protected LamiChartModel
getChartModel() {
552 * Get the chart object.
553 * @return The chart object.
555 protected Chart
getChart() {
560 * @return the toolBar
562 public ToolBar
getToolBar() {
567 * Is a selection made in the chart.
569 * @return true if there is a selection.
571 protected boolean isSelected() {
576 * Set the selection index.
578 * @param selection the index to select.
580 protected void setSelection(Set
<Integer
> selection
) {
581 fSelection
= selection
;
582 fSelected
= !selection
.isEmpty();
586 * Unset the chart selection.
588 protected void unsetSelection() {
594 * Get the current selection index.
596 * @return the current selection index.
598 protected Set
<Integer
> getSelection() {
603 public @Nullable Control
getControl() {
604 return fChart
.getParent();
608 public void refresh() {
609 Display
.getDefault().asyncExec(() -> {
610 if (!fChart
.isDisposed()) {
617 public void dispose() {
619 /* The control's DisposeListener will call super.dispose() */
623 * Get a list of all the aspect of the Y axis.
625 * @return The aspects for the Y axis
627 protected List
<LamiTableEntryAspect
> getYAxisAspects() {
629 List
<LamiTableEntryAspect
> yAxisAspects
= new ArrayList
<>();
631 for (String colName
: getChartModel().getYSeriesColumns()) {
632 yAxisAspects
.add(checkNotNull(getAspectFromName(getResultTable().getTableClass().getAspects(), colName
)));
639 * Get a list of all the aspect of the X axis.
641 * @return The aspects for the X axis
643 protected List
<LamiTableEntryAspect
> getXAxisAspects() {
645 List
<LamiTableEntryAspect
> xAxisAspects
= new ArrayList
<>();
647 for (String colName
: getChartModel().getXSeriesColumns()) {
648 xAxisAspects
.add(checkNotNull(getAspectFromName(getResultTable().getTableClass().getAspects(), colName
)));
655 * Set the ITitle object text to a substring of canonicalTitle that when
656 * rendered in the chart will fit maxPixelLength.
658 private void refreshDisplayTitle(ITitle title
, String canonicalTitle
, int maxPixelLength
) {
659 if (title
.isVisible()) {
661 String newTitle
= canonicalTitle
;
663 /* Get the title font */
664 Font font
= title
.getFont();
666 GC gc
= new GC(fParent
);
669 /* Get the length and height of the canonical title in pixels */
670 Point pixels
= gc
.stringExtent(canonicalTitle
);
673 * If the title is too long, generate a shortened version based on the
674 * average character width of the current font.
676 if (pixels
.x
> maxPixelLength
) {
677 int charwidth
= gc
.getFontMetrics().getAverageCharWidth();
681 int strLen
= ((maxPixelLength
/ charwidth
) - minimum
);
683 if (strLen
> minimum
) {
684 newTitle
= canonicalTitle
.substring(0, strLen
) + ELLIPSIS
;
690 title
.setText(newTitle
);
698 * Refresh the Chart, XAxis and YAxis titles to fit the current
701 private void refreshDisplayTitles() {
702 Rectangle chartRect
= fChart
.getClientArea();
703 Rectangle plotRect
= fChart
.getPlotArea().getClientArea();
705 ITitle chartTitle
= checkNotNull(fChart
.getTitle());
706 refreshDisplayTitle(chartTitle
, fChartTitle
, chartRect
.width
);
708 ITitle xTitle
= checkNotNull(fChart
.getAxisSet().getXAxis(0).getTitle());
709 refreshDisplayTitle(xTitle
, getXTitle(), plotRect
.width
);
711 ITitle yTitle
= checkNotNull(fChart
.getAxisSet().getYAxis(0).getTitle());
712 refreshDisplayTitle(yTitle
, getYTitle(), plotRect
.height
);
716 * Get the aspect with the given name
719 * The list of aspects to search into
721 * The name of the aspect we are looking for
722 * @return The corresponding aspect
724 protected static @Nullable LamiTableEntryAspect
getAspectFromName(List
<LamiTableEntryAspect
> aspects
, String aspectName
) {
725 for (LamiTableEntryAspect lamiTableEntryAspect
: aspects
) {
727 if (lamiTableEntryAspect
.getLabel().equals(aspectName
)) {
728 return lamiTableEntryAspect
;
736 * Refresh the axis labels to fit the current chart size.
738 protected abstract void refreshDisplayLabels();
743 protected void redraw() {
748 * Signal handler for selection update.
751 * The selection update signal
754 public void updateSelection(LamiSelectionUpdateSignal signal
) {
755 if (getResultTable().hashCode() != signal
.getSignalHash() || equals(signal
.getSource())) {
756 /* The signal is not for us */
759 setSelection(signal
.getEntryIndex());
765 * Create a tool bar on top right of the chart. Contained actions:
767 * <li>Dispose the current viewer, also known as "Close the chart"</li>
770 * This tool bar should only appear when the mouse enters the composite.
772 * @return the tool bar
774 protected ToolBar
createChartToolBar() {
775 Image removeImage
= PlatformUI
.getWorkbench().getSharedImages().getImage(ISharedImages
.IMG_ELCL_REMOVE
);
776 ToolBar toolBar
= new ToolBar(getChart(), SWT
.HORIZONTAL
);
779 toolBar
.moveAbove(null);
780 toolBar
.setVisible(false);
785 ToolItem closeButton
= new ToolItem(toolBar
, SWT
.PUSH
);
786 closeButton
.setImage(removeImage
);
787 closeButton
.setToolTipText(Messages
.LamiXYChartViewer_CloseChartToolTip
);
788 closeButton
.addSelectionListener(new SelectionListener() {
790 public void widgetSelected(@Nullable SelectionEvent e
) {
791 Composite parent
= getParent();
797 public void widgetDefaultSelected(@Nullable SelectionEvent e
) {
802 toolBar
.setLocation(new Point(getChart().getSize().x
- toolBar
.getSize().x
, 0));
804 /* Visibility toggle filter */
805 Listener toolBarVisibilityToggleListener
= e
-> {
806 if (e
.widget
instanceof Control
) {
807 Control control
= (Control
) e
.widget
;
808 Point display
= control
.toDisplay(e
.x
, e
.y
);
809 Point location
= getChart().getParent().toControl(display
);
812 * Only set to visible if we are at the right location, in the
815 boolean visible
= getChart().getBounds().contains(location
) &&
816 control
.getShell().equals(getChart().getShell());
817 getToolBar().setVisible(visible
);
821 /* Filter to make sure we hide the toolbar if we exit the window */
822 Listener hideToolBarListener
= (e
-> getToolBar().setVisible(false));
825 * Add the filters to the main Display, and remove them when we dispose
828 Display display
= getChart().getDisplay();
829 display
.addFilter(SWT
.MouseEnter
, toolBarVisibilityToggleListener
);
830 display
.addFilter(SWT
.MouseExit
, hideToolBarListener
);
832 getChart().addDisposeListener(e
-> {
833 display
.removeFilter(SWT
.MouseEnter
, toolBarVisibilityToggleListener
);
834 display
.removeFilter(SWT
.MouseExit
, hideToolBarListener
);
837 /* Reposition the tool bar on resize */
838 getChart().addListener(SWT
.Resize
, new Listener() {
840 public void handleEvent(@Nullable Event event
) {
841 toolBar
.setLocation(new Point(getChart().getSize().x
- toolBar
.getSize().x
, 0));
849 * Get a {@link LamiGraphRange} that covers all data points in the result
852 * The returned range will be the minimum and maximum of the resolved values
853 * of the passed aspects for all result entries. If <code>clampToZero</code>
854 * is true, a positive minimum value will be clamped down to zero.
857 * The aspects that the range will represent
859 * If true, a positive minimum value will be clamped down to zero
862 protected LamiGraphRange
getRange(List
<LamiTableEntryAspect
> aspects
, boolean clampToZero
) {
863 /* Find the minimum and maximum values */
864 BigDecimal min
= new BigDecimal(Long
.MAX_VALUE
);
865 BigDecimal max
= new BigDecimal(Long
.MIN_VALUE
);
866 for (LamiTableEntryAspect lamiTableEntryAspect
: aspects
) {
867 for (LamiTableEntry entry
: getResultTable().getEntries()) {
868 @Nullable Number number
= lamiTableEntryAspect
.resolveNumber(entry
);
869 if (number
!= null) {
870 BigDecimal current
= new BigDecimal(number
.toString());
871 min
= current
.min(min
);
872 max
= current
.max(max
);
878 min
= min
.min(BigDecimal
.ZERO
);
881 /* Do not allow a range with a zero delta default to 1 */
882 if (max
.equals(min
)) {
883 max
= min
.add(BigDecimal
.ONE
);
886 return new LamiGraphRange(checkNotNull(min
), checkNotNull(max
));
890 * Transform an external value into an internal value. Since SWTChart only
891 * support Double and Lami can pass Long values, loss of precision might
892 * happen. To minimize this, transform the raw values to an internal
893 * representation based on a linear transformation.
895 * The internal value =
897 * ((rawValue - rawMinimum) * (internalRangeDelta/rawRangeDelta)) +
901 * The number to transform
902 * @param internalRange
903 * The internal range definition to be used
904 * @param externalRange
905 * The external range definition to be used
906 * @return the transformed value in Double comprised inside the internal
909 protected static double getInternalDoubleValue(Number number
, LamiGraphRange internalRange
, LamiGraphRange externalRange
) {
910 BigDecimal value
= new BigDecimal(number
.toString());
912 if (externalRange
.getDelta().compareTo(BigDecimal
.ZERO
) == 0) {
913 return internalRange
.getMinimum().doubleValue();
916 BigDecimal internalValue
= value
917 .subtract(externalRange
.getMinimum())
918 .multiply(internalRange
.getDelta())
919 .divide(externalRange
.getDelta(), BIG_DECIMAL_DIVISION_SCALE
, BigDecimal
.ROUND_DOWN
)
920 .add(internalRange
.getMinimum());
922 return internalValue
.doubleValue();