a958302ff23f5f8f579514b5094b145ffa1aa7e0
[deliverable/tracecompass.git] / analysis / org.eclipse.tracecompass.analysis.lami.ui / src / org / eclipse / tracecompass / internal / provisional / analysis / lami / ui / viewers / LamiXYChartViewer.java
1 /*******************************************************************************
2 * Copyright (c) 2015, 2016 EfficiOS Inc., Michael Jeanson
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
10 package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
11
12 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
13 import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
14
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;
20 import java.util.Set;
21 import java.util.concurrent.TimeUnit;
22 import java.util.function.ToDoubleFunction;
23
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;
56
57 import com.google.common.collect.ImmutableList;
58
59 /**
60 * Abstract XYChart Viewer for LAMI views.
61 *
62 * @author Michael Jeanson
63 *
64 */
65 public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer {
66
67 /** Ellipsis character */
68 protected static final String ELLIPSIS = "…"; //$NON-NLS-1$
69
70 /**
71 * String representing unknown values. Can be present even in numerical
72 * aspects!
73 */
74 protected static final String UNKNOWN = "?"; //$NON-NLS-1$
75
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;
80
81 /**
82 * Function to use to map Strings read from the data table to doubles for
83 * use in SWTChart series.
84 */
85 protected static final ToDoubleFunction<@Nullable String> DOUBLE_MAPPER = str -> {
86 if (str == null || str.equals(UNKNOWN)) {
87 return ZERO_LONG;
88 }
89 return Double.parseDouble(str);
90 };
91
92 /**
93 * List of standard colors
94 */
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)
102 );
103
104 /**
105 * List of "light" colors (when unselected)
106 */
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)
114 );
115
116 /**
117 * Time stamp formatter for intervals in the days range.
118 */
119 protected static final LamiTimeStampFormat DAYS_FORMATTER = new LamiTimeStampFormat("dd HH:mm"); //$NON-NLS-1$
120
121 /**
122 * Time stamp formatter for intervals in the hours range.
123 */
124 protected static final LamiTimeStampFormat HOURS_FORMATTER = new LamiTimeStampFormat("HH:mm"); //$NON-NLS-1$
125
126 /**
127 * Time stamp formatter for intervals in the minutes range.
128 */
129 protected static final LamiTimeStampFormat MINUTES_FORMATTER = new LamiTimeStampFormat("mm:ss"); //$NON-NLS-1$
130
131 /**
132 * Time stamp formatter for intervals in the seconds range.
133 */
134 protected static final LamiTimeStampFormat SECONDS_FORMATTER = new LamiTimeStampFormat("ss"); //$NON-NLS-1$
135
136 /**
137 * Time stamp formatter for intervals in the milliseconds range.
138 */
139 protected static final LamiTimeStampFormat MILLISECONDS_FORMATTER = new LamiTimeStampFormat("ss.SSS"); //$NON-NLS-1$
140
141 /**
142 * Decimal formatter to display nanoseconds as seconds.
143 */
144 protected static final DecimalUnitFormat NANO_TO_SECS_FORMATTER = new LamiDecimalUnitFormat(0.000000001);
145
146 /**
147 * Default decimal formatter.
148 */
149 protected static final DecimalUnitFormat DECIMAL_FORMATTER = new LamiDecimalUnitFormat();
150
151 /** Symbol for seconds (used in the custom ns -> s conversion) */
152 private static final String SECONDS_SYMBOL = "s"; //$NON-NLS-1$
153
154 /** Symbol for nanoseconds (used in the custom ns -> s conversion) */
155 private static final String NANOSECONDS_SYMBOL = "ns"; //$NON-NLS-1$
156
157 /** Maximum amount of digits that can be represented into a double */
158 private static final int BIG_DECIMAL_DIVISION_SCALE = 22;
159
160 private final Listener fResizeListener = event -> {
161 /* Refresh the titles to fit the current chart size */
162 refreshDisplayTitles();
163
164 /* Refresh the Axis labels to fit the current chart size */
165 refreshDisplayLabels();
166 };
167
168 private final LamiResultTable fResultTable;
169 private final LamiChartModel fChartModel;
170
171 private final Chart fChart;
172
173 private final String fChartTitle;
174 private final String fXTitle;
175 private final String fYTitle;
176
177 private boolean fSelected;
178 private Set<Integer> fSelection;
179
180 private final ToolBar fToolBar;
181
182 /**
183 * Creates a Viewer instance based on SWTChart.
184 *
185 * @param parent
186 * The parent composite to draw in.
187 * @param resultTable
188 * The result table containing the data from which to build the
189 * chart
190 * @param chartModel
191 * The information about the chart to build
192 */
193 public LamiXYChartViewer(Composite parent, LamiResultTable resultTable, LamiChartModel chartModel) {
194 super(parent);
195
196 fParent = parent;
197 fResultTable = resultTable;
198 fChartModel = chartModel;
199 fSelection = new HashSet<>();
200
201 fChart = new Chart(parent, SWT.NONE);
202 fChart.addListener(SWT.Resize, fResizeListener);
203
204 /* Set Chart title */
205 fChartTitle = fResultTable.getTableClass().getTableTitle();
206
207 /* Set X axis title */
208 if (fChartModel.getXSeriesColumns().size() == 1) {
209 /*
210 * There is only 1 series in the chart, we will use its name as the
211 * Y axis (and hide the legend).
212 */
213 String seriesName = getChartModel().getXSeriesColumns().get(0);
214 // The time duration formatter converts ns to s on the axis
215 if (NANOSECONDS_SYMBOL.equals(getXAxisAspects().get(0).getUnits())) {
216 seriesName = getXAxisAspects().get(0).getName() + " (" + SECONDS_SYMBOL + ')'; //$NON-NLS-1$
217 }
218 fXTitle = seriesName;
219 } else {
220 /*
221 * There are multiple series in the chart, if they all share the same
222 * units, display that.
223 */
224 long nbDiffAspectsUnits = getXAxisAspects().stream()
225 .map(aspect -> aspect.getUnits())
226 .distinct()
227 .count();
228
229 long nbDiffAspectName = getXAxisAspects().stream()
230 .map(aspect -> aspect.getName())
231 .distinct()
232 .count();
233
234 String xBaseTitle = Messages.LamiViewer_DefaultValueName;
235 if (nbDiffAspectName == 1) {
236 xBaseTitle = getXAxisAspects().get(0).getName();
237 }
238
239 String units = getXAxisAspects().get(0).getUnits();
240 if (nbDiffAspectsUnits == 1 && units != null) {
241 /* All aspects use the same unit type */
242
243 // The time duration formatter converts ns to s on the axis
244 if (NANOSECONDS_SYMBOL.equals(units)) {
245 units = SECONDS_SYMBOL;
246 }
247 fXTitle = xBaseTitle + " (" + units + ')'; //$NON-NLS-1$
248 } else {
249 /* Various unit types, just say "Value" */
250 fXTitle = nullToEmptyString(xBaseTitle);
251 }
252 }
253
254 /* Set Y axis title */
255 if (fChartModel.getYSeriesColumns().size() == 1) {
256 /*
257 * There is only 1 series in the chart, we will use its name as the
258 * Y axis (and hide the legend).
259 */
260 String seriesName = getChartModel().getYSeriesColumns().get(0);
261 // The time duration formatter converts ns to s on the axis
262 if (NANOSECONDS_SYMBOL.equals(getYAxisAspects().get(0).getUnits())) {
263 seriesName = getYAxisAspects().get(0).getName() + " (" + SECONDS_SYMBOL + ')'; //$NON-NLS-1$
264 }
265 fYTitle = seriesName;
266 fChart.getLegend().setVisible(false);
267 } else {
268 /*
269 * There are multiple series in the chart, if they all share the same
270 * units, display that.
271 */
272 long nbDiffAspectsUnits = getYAxisAspects().stream()
273 .map(aspect -> aspect.getUnits())
274 .distinct()
275 .count();
276
277 long nbDiffAspectName = getYAxisAspects().stream()
278 .map(aspect -> aspect.getName())
279 .distinct()
280 .count();
281
282 String yBaseTitle = Messages.LamiViewer_DefaultValueName;
283 if (nbDiffAspectName == 1) {
284 yBaseTitle = getYAxisAspects().get(0).getName();
285 }
286
287 String units = getYAxisAspects().get(0).getUnits();
288 if (nbDiffAspectsUnits == 1 && units != null) {
289 /* All aspects use the same unit type */
290
291 // The time duration formatter converts ns to s on the axis
292 if (NANOSECONDS_SYMBOL.equals(units)) {
293 units = SECONDS_SYMBOL;
294 }
295 fYTitle = yBaseTitle + " (" + units + ')'; //$NON-NLS-1$
296 } else {
297 /* Various unit types, don't display any units */
298 fYTitle = nullToEmptyString(yBaseTitle);
299 }
300
301 /* Put legend at the bottom */
302 fChart.getLegend().setPosition(SWT.BOTTOM);
303 }
304
305 /* Set all titles and labels font color to black */
306 fChart.getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
307 fChart.getAxisSet().getXAxis(0).getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
308 fChart.getAxisSet().getYAxis(0).getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
309 fChart.getAxisSet().getXAxis(0).getTick().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
310 fChart.getAxisSet().getYAxis(0).getTick().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
311
312 /* Set X label 90 degrees */
313 fChart.getAxisSet().getXAxis(0).getTick().setTickLabelAngle(90);
314
315 /* Refresh the titles to fit the current chart size */
316 refreshDisplayTitles();
317
318 fToolBar = createChartToolBar();
319
320 fChart.addDisposeListener(e -> {
321 /* Dispose resources of this class */
322 LamiXYChartViewer.super.dispose();
323 });
324 }
325
326 /**
327 * Util method to check if a list of aspects are all continuous.
328 *
329 * @param axisAspects
330 * The list of aspects to check.
331 * @return true is all aspects are continuous, otherwise false.
332 */
333 protected static boolean areAspectsContinuous(List<LamiTableEntryAspect> axisAspects) {
334 return axisAspects.stream().allMatch(aspect -> aspect.isContinuous());
335 }
336
337 /**
338 * Util method to check if a list of aspects are all time stamps.
339 *
340 * @param axisAspects
341 * The list of aspects to check.
342 * @return true is all aspects are time stamps, otherwise false.
343 */
344 protected static boolean areAspectsTimeStamp(List<LamiTableEntryAspect> axisAspects) {
345 return axisAspects.stream().allMatch(aspect -> aspect.isTimeStamp());
346 }
347
348 /**
349 * Util method to check if a list of aspects are all time durations.
350 *
351 * @param axisAspects
352 * The list of aspects to check.
353 * @return true is all aspects are time durations, otherwise false.
354 */
355 protected static boolean areAspectsTimeDuration(List<LamiTableEntryAspect> axisAspects) {
356 return axisAspects.stream().allMatch(aspect -> aspect.isTimeDuration());
357 }
358
359 /**
360 * Util method that will return a formatter based on the aspects linked to an axis
361 *
362 * If all aspects are time stamps, return a timestamp formatter tuned to the interval.
363 * If all aspects are time durations, return the nanoseconds to seconds formatter.
364 * Otherwise, return the generic decimal formatter.
365 *
366 * @param axisAspects
367 * The list of aspects of the axis.
368 * @param entries
369 * The list of entries of the chart.
370 * @param internalRange
371 * The internal range for value transformation
372 * @param externalRange
373 * The external range for value transformation
374 * @return a formatter for the axis.
375 */
376 protected static Format getContinuousAxisFormatter(List<LamiTableEntryAspect> axisAspects, List<LamiTableEntry> entries , @Nullable LamiGraphRange internalRange, @Nullable LamiGraphRange externalRange) {
377
378 Format formatter = DECIMAL_FORMATTER;
379
380 if (areAspectsTimeStamp(axisAspects)) {
381 /* Set a TimeStamp formatter depending on the duration between the first and last value */
382 BigDecimal max = new BigDecimal(Long.MIN_VALUE);
383 BigDecimal min = new BigDecimal(Long.MAX_VALUE);
384
385 for (LamiTableEntry entry : entries) {
386 for (LamiTableEntryAspect aspect : axisAspects) {
387 @Nullable Number number = aspect.resolveNumber(entry);
388 if (number != null) {
389 BigDecimal current = new BigDecimal(number.toString());
390 max = current.max(max);
391 min = current.min(min);
392 }
393 }
394 }
395
396 long duration = max.subtract(min).longValue();
397 if (duration > TimeUnit.DAYS.toNanos(1)) {
398 formatter = DAYS_FORMATTER;
399 } else if (duration > TimeUnit.HOURS.toNanos(1)) {
400 formatter = HOURS_FORMATTER;
401 } else if (duration > TimeUnit.MINUTES.toNanos(1)) {
402 formatter = MINUTES_FORMATTER;
403 } else if (duration > TimeUnit.SECONDS.toNanos(15)) {
404 formatter = SECONDS_FORMATTER;
405 } else {
406 formatter = MILLISECONDS_FORMATTER;
407 }
408 ((LamiTimeStampFormat) formatter).setInternalRange(internalRange);
409 ((LamiTimeStampFormat) formatter).setExternalRange(externalRange);
410
411 } else if (areAspectsTimeDuration(axisAspects)) {
412 /* Set the time duration formatter. */
413 formatter = NANO_TO_SECS_FORMATTER;
414 ((LamiDecimalUnitFormat) formatter).setInternalRange(internalRange);
415 ((LamiDecimalUnitFormat) formatter).setExternalRange(externalRange);
416
417 } else {
418 /*
419 * For other numeric aspects, use the default lami decimal unit
420 * formatter.
421 */
422 formatter = DECIMAL_FORMATTER;
423 ((LamiDecimalUnitFormat) formatter).setInternalRange(internalRange);
424 ((LamiDecimalUnitFormat) formatter).setExternalRange(externalRange);
425 }
426
427 return formatter;
428 }
429
430 /**
431 * Get the chart result table.
432 *
433 * @return The chart result table.
434 */
435 protected LamiResultTable getResultTable() {
436 return fResultTable;
437 }
438
439 /**
440 * Get the chart model.
441 *
442 * @return The chart model.
443 */
444 protected LamiChartModel getChartModel() {
445 return fChartModel;
446 }
447
448 /**
449 * Get the chart object.
450 * @return The chart object.
451 */
452 protected Chart getChart() {
453 return fChart;
454 }
455
456 /**
457 * @return the toolBar
458 */
459 public ToolBar getToolBar() {
460 return fToolBar;
461 }
462
463 /**
464 * Is a selection made in the chart.
465 *
466 * @return true if there is a selection.
467 */
468 protected boolean isSelected() {
469 return fSelected;
470 }
471
472 /**
473 * Set the selection index.
474 *
475 * @param selection the index to select.
476 */
477 protected void setSelection(Set<Integer> selection) {
478 fSelection = selection;
479 fSelected = !selection.isEmpty();
480 }
481
482 /**
483 * Unset the chart selection.
484 */
485 protected void unsetSelection() {
486 fSelection.clear();
487 fSelected = false;
488 }
489
490 /**
491 * Get the current selection index.
492 *
493 * @return the current selection index.
494 */
495 protected Set<Integer> getSelection() {
496 return fSelection;
497 }
498
499 @Override
500 public @Nullable Control getControl() {
501 return fChart.getParent();
502 }
503
504 @Override
505 public void refresh() {
506 Display.getDefault().asyncExec(() -> {
507 if (!fChart.isDisposed()) {
508 fChart.redraw();
509 }
510 });
511 }
512
513 @Override
514 public void dispose() {
515 fChart.dispose();
516 /* The control's DisposeListener will call super.dispose() */
517 }
518
519 /**
520 * Get a list of all the aspect of the Y axis.
521 *
522 * @return The aspects for the Y axis
523 */
524 protected List<LamiTableEntryAspect> getYAxisAspects() {
525
526 List<LamiTableEntryAspect> yAxisAspects = new ArrayList<>();
527
528 for (String colName : getChartModel().getYSeriesColumns()) {
529 yAxisAspects.add(checkNotNull(getAspectFromName(getResultTable().getTableClass().getAspects(), colName)));
530 }
531
532 return yAxisAspects;
533 }
534
535 /**
536 * Get a list of all the aspect of the X axis.
537 *
538 * @return The aspects for the X axis
539 */
540 protected List<LamiTableEntryAspect> getXAxisAspects() {
541
542 List<LamiTableEntryAspect> xAxisAspects = new ArrayList<>();
543
544 for (String colName : getChartModel().getXSeriesColumns()) {
545 xAxisAspects.add(checkNotNull(getAspectFromName(getResultTable().getTableClass().getAspects(), colName)));
546 }
547
548 return xAxisAspects;
549 }
550
551 /**
552 * Set the ITitle object text to a substring of canonicalTitle that when
553 * rendered in the chart will fit maxPixelLength.
554 */
555 private void refreshDisplayTitle(ITitle title, String canonicalTitle, int maxPixelLength) {
556 if (title.isVisible()) {
557
558 String newTitle = canonicalTitle;
559
560 /* Get the title font */
561 Font font = title.getFont();
562
563 GC gc = new GC(fParent);
564 gc.setFont(font);
565
566 /* Get the length and height of the canonical title in pixels */
567 Point pixels = gc.stringExtent(canonicalTitle);
568
569 /*
570 * If the title is too long, generate a shortened version based on the
571 * average character width of the current font.
572 */
573 if (pixels.x > maxPixelLength) {
574 int charwidth = gc.getFontMetrics().getAverageCharWidth();
575
576 int minimum = 3;
577
578 int strLen = ((maxPixelLength / charwidth) - minimum);
579
580 if (strLen > minimum) {
581 newTitle = canonicalTitle.substring(0, strLen) + ELLIPSIS;
582 } else {
583 newTitle = ELLIPSIS;
584 }
585 }
586
587 title.setText(newTitle);
588
589 // Cleanup
590 gc.dispose();
591 }
592 }
593
594 /**
595 * Refresh the Chart, XAxis and YAxis titles to fit the current
596 * chart size.
597 */
598 private void refreshDisplayTitles() {
599 Rectangle chartRect = fChart.getClientArea();
600 Rectangle plotRect = fChart.getPlotArea().getClientArea();
601
602 ITitle chartTitle = checkNotNull(fChart.getTitle());
603 refreshDisplayTitle(chartTitle, fChartTitle, chartRect.width);
604
605 ITitle xTitle = checkNotNull(fChart.getAxisSet().getXAxis(0).getTitle());
606 refreshDisplayTitle(xTitle, fXTitle, plotRect.width);
607
608 ITitle yTitle = checkNotNull(fChart.getAxisSet().getYAxis(0).getTitle());
609 refreshDisplayTitle(yTitle, fYTitle, plotRect.height);
610 }
611
612 /**
613 * Get the aspect with the given name
614 *
615 * @param aspects
616 * The list of aspects to search into
617 * @param aspectName
618 * The name of the aspect we are looking for
619 * @return The corresponding aspect
620 */
621 protected static @Nullable LamiTableEntryAspect getAspectFromName(List<LamiTableEntryAspect> aspects, String aspectName) {
622 for (LamiTableEntryAspect lamiTableEntryAspect : aspects) {
623
624 if (lamiTableEntryAspect.getLabel().equals(aspectName)) {
625 return lamiTableEntryAspect;
626 }
627 }
628
629 return null;
630 }
631
632 /**
633 * Refresh the axis labels to fit the current chart size.
634 */
635 protected abstract void refreshDisplayLabels();
636
637 /**
638 * Redraw the chart.
639 */
640 protected void redraw() {
641 refresh();
642 }
643
644 /**
645 * Signal handler for selection update.
646 *
647 * @param signal
648 * The selection update signal
649 */
650 @TmfSignalHandler
651 public void updateSelection(LamiSelectionUpdateSignal signal) {
652 if (getResultTable().hashCode() != signal.getSignalHash() || equals(signal.getSource())) {
653 /* The signal is not for us */
654 return;
655 }
656 setSelection(signal.getEntryIndex());
657
658 redraw();
659 }
660
661 /**
662 * Create a tool bar on top right of the chart. Contained actions:
663 * <ul>
664 * <li>Dispose the current viewer, also known as "Close the chart"</li>
665 * </ul>
666 *
667 * This tool bar should only appear when the mouse enters the composite.
668 *
669 * @return the tool bar
670 */
671 protected ToolBar createChartToolBar() {
672 Image removeImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_ELCL_REMOVE);
673 ToolBar toolBar = new ToolBar(getChart(), SWT.HORIZONTAL);
674
675 /* Default state */
676 toolBar.moveAbove(null);
677 toolBar.setVisible(false);
678
679 /*
680 * Close chart button
681 */
682 ToolItem closeButton = new ToolItem(toolBar, SWT.PUSH);
683 closeButton.setImage(removeImage);
684 closeButton.setToolTipText(Messages.LamiXYChartViewer_CloseChartToolTip);
685 closeButton.addSelectionListener(new SelectionListener() {
686 @Override
687 public void widgetSelected(@Nullable SelectionEvent e) {
688 Composite parent = getParent();
689 dispose();
690 parent.layout();
691 }
692
693 @Override
694 public void widgetDefaultSelected(@Nullable SelectionEvent e) {
695 }
696 });
697
698 toolBar.pack();
699 toolBar.setLocation(new Point(getChart().getSize().x - toolBar.getSize().x, 0));
700
701 /* Visibility toggle filter */
702 Listener toolBarVisibilityToggleListener = e -> {
703 if (e.widget instanceof Control) {
704 Control control = (Control) e.widget;
705 Point display = control.toDisplay(e.x, e.y);
706 Point location = getChart().getParent().toControl(display);
707
708 /*
709 * Only set to visible if we are at the right location, in the
710 * right shell.
711 */
712 boolean visible = getChart().getBounds().contains(location) &&
713 control.getShell().equals(getChart().getShell());
714 getToolBar().setVisible(visible);
715 }
716 };
717
718 /* Filter to make sure we hide the toolbar if we exit the window */
719 Listener hideToolBarListener = (e -> getToolBar().setVisible(false));
720
721 /*
722 * Add the filters to the main Display, and remove them when we dispose
723 * the chart.
724 */
725 Display display = getChart().getDisplay();
726 display.addFilter(SWT.MouseEnter, toolBarVisibilityToggleListener);
727 display.addFilter(SWT.MouseExit, hideToolBarListener);
728
729 getChart().addDisposeListener(e -> {
730 display.removeFilter(SWT.MouseEnter, toolBarVisibilityToggleListener);
731 display.removeFilter(SWT.MouseExit, hideToolBarListener);
732 });
733
734 /* Reposition the tool bar on resize */
735 getChart().addListener(SWT.Resize, new Listener() {
736 @Override
737 public void handleEvent(@Nullable Event event) {
738 toolBar.setLocation(new Point(getChart().getSize().x - toolBar.getSize().x, 0));
739 }
740 });
741
742 return toolBar;
743 }
744
745 /**
746 * Get a {@link LamiGraphRange} that covers all data points in the result
747 * table.
748 * <p>
749 * The returned range will be the minimum and maximum of the resolved values
750 * of the passed aspects for all result entries. If <code>clampToZero</code>
751 * is true, a positive minimum value will be clamped down to zero.
752 *
753 * @param aspects
754 * The aspects that the range will represent
755 * @param clampToZero
756 * If true, a positive minimum value will be clamped down to zero
757 * @return the range
758 */
759 protected LamiGraphRange getRange(List<LamiTableEntryAspect> aspects, boolean clampToZero) {
760 /* Find the minimum and maximum values */
761 BigDecimal min = new BigDecimal(Long.MAX_VALUE);
762 BigDecimal max = new BigDecimal(Long.MIN_VALUE);
763 for (LamiTableEntryAspect lamiTableEntryAspect : aspects) {
764 for (LamiTableEntry entry : getResultTable().getEntries()) {
765 @Nullable Number number = lamiTableEntryAspect.resolveNumber(entry);
766 if (number != null) {
767 BigDecimal current = new BigDecimal(number.toString());
768 min = current.min(min);
769 max = current.max(max);
770 }
771 }
772 }
773
774 if (clampToZero) {
775 min.min(BigDecimal.ZERO);
776 }
777
778 /* Do not allow a range with a zero delta default to 1 */
779 if (max.equals(min)) {
780 max = min.add(BigDecimal.ONE);
781 }
782
783 return new LamiGraphRange(checkNotNull(min), checkNotNull(max));
784 }
785
786 /**
787 * Transform an external value into an internal value. Since SWTChart only
788 * support Double and Lami can pass Long values, loss of precision might
789 * happen. To minimize this, transform the raw values to an internal
790 * representation based on a linear transformation.
791 *
792 * The internal value =
793 *
794 * ((rawValue - rawMinimum) * (internalRangeDelta/rawRangeDelta)) +
795 * internalMinimum
796 *
797 * @param number
798 * The number to transform
799 * @param internalRange
800 * The internal range definition to be used
801 * @param externalRange
802 * The external range definition to be used
803 * @return the transformed value in Double comprised inside the internal
804 * range
805 */
806 protected static double getInternalDoubleValue(Number number, LamiGraphRange internalRange, LamiGraphRange externalRange) {
807 BigDecimal value = new BigDecimal(number.toString());
808
809 if (externalRange.getDelta().compareTo(BigDecimal.ZERO) == 0) {
810 return internalRange.getMinimum().doubleValue();
811 }
812
813 BigDecimal internalValue = value
814 .subtract(externalRange.getMinimum())
815 .multiply(internalRange.getDelta())
816 .divide(externalRange.getDelta(), BIG_DECIMAL_DIVISION_SCALE, BigDecimal.ROUND_DOWN)
817 .add(internalRange.getMinimum());
818
819 return internalValue.doubleValue();
820 }
821 }
This page took 0.065241 seconds and 4 git commands to generate.