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