analysis.lami: correctly handle Number (double, long etc.) type graphing
[deliverable/tracecompass.git] / analysis / org.eclipse.tracecompass.analysis.lami.ui / src / org / eclipse / tracecompass / internal / provisional / analysis / lami / ui / viewers / LamiScatterViewer.java
CommitLineData
4208b510
AM
1/*******************************************************************************
2 * Copyright (c) 2016 EfficiOS Inc., Jonathan Rajotte-Julien
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;
13
5b973e7c 14import java.math.BigDecimal;
4208b510
AM
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.HashMap;
18import java.util.HashSet;
19import java.util.Iterator;
20import java.util.List;
21import java.util.Map;
22import java.util.Set;
23import java.util.TreeSet;
24import java.util.stream.Collectors;
25import java.util.stream.Stream;
26
27import org.eclipse.jdt.annotation.NonNull;
28import org.eclipse.jdt.annotation.Nullable;
29import org.eclipse.swt.SWT;
30import org.eclipse.swt.events.MouseAdapter;
31import org.eclipse.swt.events.MouseEvent;
32import org.eclipse.swt.events.MouseMoveListener;
33import org.eclipse.swt.events.PaintEvent;
34import org.eclipse.swt.events.PaintListener;
35import org.eclipse.swt.graphics.Color;
36import org.eclipse.swt.graphics.GC;
37import org.eclipse.swt.graphics.Point;
38import org.eclipse.swt.widgets.Composite;
39import org.eclipse.swt.widgets.Display;
40import org.eclipse.swt.widgets.Event;
41import org.eclipse.swt.widgets.Listener;
42import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
43import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
44import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel.ChartType;
4208b510
AM
45import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
46import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
5b973e7c 47import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.format.LamiLabelFormat;
4208b510
AM
48import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal;
49import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
50import org.swtchart.IAxisTick;
51import org.swtchart.ILineSeries;
52import org.swtchart.ISeries;
53import org.swtchart.ISeries.SeriesType;
54import org.swtchart.LineStyle;
55
56import com.google.common.collect.BiMap;
57import com.google.common.collect.HashBiMap;
58import com.google.common.collect.Iterators;
59
60/**
61 * XY Scatter chart viewer for Lami views
62 *
63 * @author Jonathan Rajotte-Julien
64 */
65public class LamiScatterViewer extends LamiXYChartViewer {
66
67 private static final int SELECTION_SNAP_RANGE_MULTIPLIER = 20;
68 private static final int SELECTION_CROSS_SIZE_MULTIPLIER = 3;
69
70 private final Map<ISeries, List<Integer>> fIndexMapping;
71
5b973e7c
JR
72 /* Use a scale from 0 to 1 internally for both axes */
73 private LamiGraphRange fXInternalRange = new LamiGraphRange(checkNotNull(BigDecimal.ZERO), checkNotNull(BigDecimal.ONE));
74 private LamiGraphRange fYInternalRange = new LamiGraphRange(checkNotNull(BigDecimal.ZERO), checkNotNull(BigDecimal.ONE));
75
76 private @Nullable LamiGraphRange fXExternalRange = null;
77 private @Nullable LamiGraphRange fYExternalRange = null;
78
4208b510
AM
79 /* The current data point for the hovering cross */
80 private Point fHoveringCrossDataPoint;
81
82 /**
83 * Constructor
84 *
85 * @param parent
86 * parent
87 * @param resultTable
88 * Result table populating this chart
89 * @param graphModel
90 * Model of this chart
91 */
92 public LamiScatterViewer(Composite parent, LamiResultTable resultTable, LamiChartModel graphModel) {
93 super(parent, resultTable, graphModel);
94 if (getChartModel().getChartType() != ChartType.XY_SCATTER) {
95 throw new IllegalStateException("Chart type not a Scatter Chart " + getChartModel().getChartType().toString()); //$NON-NLS-1$
96 }
97
98 /* Inspect X series */
99 fIndexMapping = new HashMap<>();
100
101 fHoveringCrossDataPoint = new Point(-1, -1);
102
103 List<LamiTableEntryAspect> xAxisAspects = getXAxisAspects();
104 if (xAxisAspects.stream().distinct().count() == 1) {
105 LamiTableEntryAspect singleXAspect = xAxisAspects.get(0);
106 xAxisAspects.clear();
107 xAxisAspects.add(singleXAspect);
108 }
109
110 BiMap<@Nullable String, Integer> xMap = checkNotNull(HashBiMap.create());
111 boolean xIsLog = graphModel.xAxisIsLog();
112
113 boolean areXAspectsContinuous = areAspectsContinuous(xAxisAspects);
114 boolean areXAspectsTimeStamp = areAspectsTimeStamp(xAxisAspects);
115
116 /* Check all aspect are the same type */
117 for (LamiTableEntryAspect aspect : xAxisAspects) {
118 if (aspect.isContinuous() != areXAspectsContinuous) {
119 throw new IllegalStateException("Some X aspects are continuous and some are not"); //$NON-NLS-1$
120 }
121 if (aspect.isTimeStamp() != areXAspectsTimeStamp) {
122 throw new IllegalStateException("Some X aspects are time based and some are not"); //$NON-NLS-1$
123 }
124 }
125
126 /*
127 * When xAxisAspects are discrete create a map for all values of all
128 * series
129 */
130 if (!areXAspectsContinuous) {
131 generateLabelMap(xAxisAspects, checkNotNull(xMap));
5b973e7c
JR
132 } else {
133 /*
134 * Always clamp the range to min and max
135 *
136 * TODO: in the future this could be based on the result of the
137 * delta between max and min multiplied by a ratio like it is done in
138 * LibreOffice Calc
139 */
140 fXExternalRange = getRange(xAxisAspects, false);
4208b510
AM
141 }
142
143 /*
144 * Create Y series
145 */
146 List<LamiTableEntryAspect> yAxisAspects = getYAxisAspects();
147 BiMap<@Nullable String, Integer> yMap = checkNotNull(HashBiMap.create());
148 boolean yIsLog = graphModel.yAxisIsLog();
149
150 boolean areYAspectsContinuous = areAspectsContinuous(yAxisAspects);
151 boolean areYAspectsTimeStamp = areAspectsTimeStamp(yAxisAspects);
152
153 /* Check all aspect are the same type */
154 for (LamiTableEntryAspect aspect : yAxisAspects) {
155 if (aspect.isContinuous() != areYAspectsContinuous) {
156 throw new IllegalStateException("Some Y aspects are continuous and some are not"); //$NON-NLS-1$
157 }
158 if (aspect.isTimeStamp() != areYAspectsTimeStamp) {
159 throw new IllegalStateException("Some Y aspects are time based and some are not"); //$NON-NLS-1$
160 }
161 }
162
163 /*
164 * When yAspects are discrete create a map for all values of all series
165 */
166 if (!areYAspectsContinuous) {
167 generateLabelMap(yAxisAspects, yMap);
5b973e7c
JR
168 } else {
169 /*
170 * Only clamp the range to the minimum value if it is a time stamp since
171 * plotting from 1970 would make little sense.
172 */
173 fYExternalRange = getRange(yAxisAspects, areYAspectsTimeStamp);
4208b510
AM
174 }
175
176 /* Plot the series */
177 int index = 0;
178 for (LamiTableEntryAspect yAspect : getYAxisAspects()) {
5b973e7c 179 String name;
4208b510
AM
180 LamiTableEntryAspect xAspect;
181 if (xAxisAspects.size() == 1) {
182 /* Always map to the same x series */
183 xAspect = xAxisAspects.get(0);
184 name = yAspect.getLabel();
185 } else {
186 xAspect = xAxisAspects.get(index);
187 name = (yAspect.getName() + ' ' + Messages.LamiScatterViewer_by + ' ' + xAspect.getName());
188 }
189
5b973e7c
JR
190 List<@Nullable Double> xDoubleSeries;
191 List<@Nullable Double> yDoubleSeries;
4208b510
AM
192
193 if (xAspect.isContinuous()) {
5b973e7c
JR
194 xDoubleSeries = getResultTable().getEntries().stream()
195 .map(entry -> {
196 Number number = xAspect.resolveNumber(entry);
197 if (number != null && fXExternalRange != null) {
198 return getInternalDoubleValue(number, fXInternalRange, fXExternalRange);
199 }
200 return null;
201 })
202 .collect(Collectors.toList());
4208b510 203 } else {
5b973e7c
JR
204 xDoubleSeries = getResultTable().getEntries().stream()
205 .map(entry -> {
206 String string = xAspect.resolveString(entry);
207 Integer value = xMap.get(string);
208 if (value != null) {
209 return Double.valueOf(value.doubleValue());
210 }
211 return null;
212 })
213 .collect(Collectors.toList());
4208b510
AM
214 }
215
216 if (yAspect.isContinuous()) {
5b973e7c
JR
217 yDoubleSeries = getResultTable().getEntries().stream()
218 .map(entry -> {
219 Number number = yAspect.resolveNumber(entry);
220 if (number != null && fYExternalRange != null) {
221 return getInternalDoubleValue(number, fYInternalRange, fYExternalRange);
222 }
223 return null;
224 })
225 .collect(Collectors.toList());
4208b510 226 } else {
5b973e7c
JR
227 yDoubleSeries = getResultTable().getEntries().stream()
228 .map(entry -> {
229 String string = yAspect.resolveString(entry);
230 Integer value = yMap.get(string);
231 if (value != null) {
232 return Double.valueOf(value.doubleValue());
233 }
234 return null;
235 })
236 .collect(Collectors.toList());
4208b510
AM
237 }
238
239 List<@Nullable Double> validXDoubleSeries = new ArrayList<>();
240 List<@Nullable Double> validYDoubleSeries = new ArrayList<>();
241 List<Integer> indexSeriesCorrespondance = new ArrayList<>();
242
243 if (xDoubleSeries.size() != yDoubleSeries.size()) {
244 throw new IllegalStateException("Series sizes don't match!"); //$NON-NLS-1$
245 }
246
247 /* Check for invalid tuple value. Any null elements are invalid */
248 for (int i = 0; i < xDoubleSeries.size(); i++) {
249 Double xValue = xDoubleSeries.get(i);
250 Double yValue = yDoubleSeries.get(i);
251 if (xValue == null || yValue == null) {
252 /* Reject this tuple */
253 continue;
254 }
5b973e7c 255 if ((xIsLog && xValue <= ZERO_DOUBLE) || (yIsLog && yValue <= ZERO_DOUBLE)) {
4208b510
AM
256 /*
257 * Equal or less than 0 values can't be plotted on log scale
258 */
259 continue;
260 }
261 validXDoubleSeries.add(xValue);
262 validYDoubleSeries.add(yValue);
263 indexSeriesCorrespondance.add(i);
264 }
265
266 if (validXDoubleSeries.isEmpty() || validXDoubleSeries.isEmpty()) {
267 /* No need to plot an empty series */
268 index++;
269 continue;
270 }
271
272 ILineSeries scatterSeries = (ILineSeries) getChart().getSeriesSet().createSeries(SeriesType.LINE, name);
273 scatterSeries.setLineStyle(LineStyle.NONE);
274
275 double[] xserie = validXDoubleSeries.stream().mapToDouble(elem -> checkNotNull(elem).doubleValue()).toArray();
276 double[] yserie = validYDoubleSeries.stream().mapToDouble(elem -> checkNotNull(elem).doubleValue()).toArray();
277 scatterSeries.setXSeries(xserie);
278 scatterSeries.setYSeries(yserie);
279 fIndexMapping.put(scatterSeries, indexSeriesCorrespondance);
280 index++;
281 }
282
283 /* Modify x axis related chart styling */
284 IAxisTick xTick = getChart().getAxisSet().getXAxis(0).getTick();
285 if (areXAspectsContinuous) {
5b973e7c 286 xTick.setFormat(getContinuousAxisFormatter(xAxisAspects, getResultTable().getEntries(), fXInternalRange, fXExternalRange));
4208b510
AM
287 } else {
288 xTick.setFormat(new LamiLabelFormat(checkNotNull(xMap)));
289 updateTickMark(checkNotNull(xMap), xTick, getChart().getPlotArea().getSize().x);
290
291 /* Remove vertical grid line */
292 getChart().getAxisSet().getXAxis(0).getGrid().setStyle(LineStyle.NONE);
293 }
294
295 /* Modify Y axis related chart styling */
296 IAxisTick yTick = getChart().getAxisSet().getYAxis(0).getTick();
297 if (areYAspectsContinuous) {
5b973e7c 298 yTick.setFormat(getContinuousAxisFormatter(yAxisAspects, getResultTable().getEntries(), fYInternalRange, fYExternalRange));
4208b510
AM
299 } else {
300 yTick.setFormat(new LamiLabelFormat(checkNotNull(yMap)));
301 updateTickMark(checkNotNull(yMap), yTick, getChart().getPlotArea().getSize().y);
302
4208b510
AM
303 /* Remove horizontal grid line */
304 getChart().getAxisSet().getYAxis(0).getGrid().setStyle(LineStyle.NONE);
305 }
306
12dbc0fc
JR
307 /*
308 * SWTChart workaround: SWTChart fiddles with tick mark visibility based
309 * on the fact that it can parse the label to double or not.
310 *
311 * If the label happens to be a double, it checks for the presence of
312 * that value in its own tick labels to decide if it should add it or
313 * not. If it happens that the parsed value is already present in its
314 * map, the tick gets a visibility of false.
315 *
316 * The X axis does not have this problem since SWTCHART checks on label
317 * angle, and if it is != 0 simply does no logic regarding visibility.
318 * So simply set a label angle of 1 to the axis.
319 */
320 yTick.setTickLabelAngle(1);
321
4208b510
AM
322 setLineSeriesColor();
323
324 /* Put log scale if necessary */
325 if (xIsLog && areXAspectsContinuous && !areXAspectsTimeStamp) {
326 Stream.of(getChart().getAxisSet().getXAxes()).forEach(axis -> axis.enableLogScale(xIsLog));
327 }
328
329 if (yIsLog && areYAspectsContinuous && !areYAspectsTimeStamp) {
330 /* Set the axis as logscale */
331 Stream.of(getChart().getAxisSet().getYAxes()).forEach(axis -> axis.enableLogScale(yIsLog));
332 }
333 getChart().getAxisSet().adjustRange();
334
335 /*
336 * Selection listener
337 */
338 getChart().getPlotArea().addMouseListener(new LamiScatterMouseDownListener());
339
340 /*
341 * Hovering cross listener
342 */
343 getChart().getPlotArea().addMouseMoveListener(new HoveringCrossListener());
344
345 /*
346 * Mouse exit listener: reset state of hovering cross on mouse exit.
347 */
348 getChart().getPlotArea().addListener(SWT.MouseExit, new Listener() {
349
350 @Override
351 public void handleEvent(@Nullable Event event) {
352 if (event != null) {
353 fHoveringCrossDataPoint.x = -1;
354 fHoveringCrossDataPoint.y = -1;
355 redraw();
356 }
357 }
358 });
359
360 /*
361 * Selections and hovering cross painting
362 */
363 getChart().getPlotArea().addPaintListener(new LamiScatterPainterListener());
364
365 /* On resize check for axis tick updating */
366 getChart().addListener(SWT.Resize, new Listener() {
367 @Override
368 public void handleEvent(@Nullable Event event) {
369 if (yTick.getFormat() instanceof LamiLabelFormat) {
370 updateTickMark(checkNotNull(yMap), yTick, getChart().getPlotArea().getSize().y);
371 }
372 if (xTick.getFormat() instanceof LamiLabelFormat) {
373 updateTickMark(checkNotNull(xMap), xTick, getChart().getPlotArea().getSize().x);
374 }
375 }
376 });
377 }
378
379 private void generateLabelMap(List<LamiTableEntryAspect> aspects, BiMap<@Nullable String, Integer> map) {
380 TreeSet<@Nullable String> set = new TreeSet<>();
381 for (LamiTableEntryAspect aspect : aspects) {
382 for (LamiTableEntry entry : getResultTable().getEntries()) {
383 String string = aspect.resolveString(entry);
384 if (string != null) {
385 set.add(string);
386 }
387 }
388 }
389 /* Ordered label mapping to double */
390 for (String string : set) {
391 map.put(string, map.size());
392 }
393 }
394
395 /**
396 * Set the chart series colors.
397 */
398 private void setLineSeriesColor() {
399 Iterator<Color> colorsIt;
400
401 colorsIt = Iterators.cycle(COLORS);
402
403 for (ISeries series : getChart().getSeriesSet().getSeries()) {
404 ((ILineSeries) series).setSymbolColor((colorsIt.next()));
405 /*
406 * Generate initial array of Color to enable per point color change
407 * on selection in the future
408 */
409 ArrayList<Color> colors = new ArrayList<>();
410 for (int i = 0; i < series.getXSeries().length; i++) {
411 Color color = ((ILineSeries) series).getSymbolColor();
412 colors.add(checkNotNull(color));
413 }
414 ((ILineSeries) series).setSymbolColors(colors.toArray(new Color[colors.size()]));
415 }
416 }
417
418 // ------------------------------------------------------------------------
419 // Listeners
420 // ------------------------------------------------------------------------
421
422 private final class HoveringCrossListener implements MouseMoveListener {
423
424 @Override
425 public void mouseMove(@Nullable MouseEvent e) {
426 if (e == null) {
427 return;
428 }
429 ISeries[] series = getChart().getSeriesSet().getSeries();
430 @Nullable Point closest = null;
431 double closestDistance = -1.0;
432
433 for (ISeries oneSeries : series) {
434 ILineSeries lineSerie = (ILineSeries) oneSeries;
435 for (int i = 0; i < lineSerie.getXSeries().length; i++) {
436 Point dataPoint = lineSerie.getPixelCoordinates(i);
437
438 /*
439 * Find the distance between the data point and the mouse
440 * location and compare it to the symbol size * the range
441 * multiplier, so when a user hovers the mouse near the dot
442 * the cursor cross snaps to it.
443 */
444 int snapRangeRadius = lineSerie.getSymbolSize() * SELECTION_SNAP_RANGE_MULTIPLIER;
445
446 /*
447 * FIXME if and only if performance of this code is an issue
448 * for large sets, this can be accelerated by getting the
449 * distance squared, and if it is smaller than
450 * snapRangeRadius squared, then check hypot.
451 */
452 double distance = Math.hypot(dataPoint.x - e.x, dataPoint.y - e.y);
453 if (distance < snapRangeRadius) {
454 if (closestDistance == -1 || distance < closestDistance) {
455 closest = dataPoint;
456 closestDistance = distance;
457 }
458 }
459 }
460 }
461 if (closest != null) {
462 fHoveringCrossDataPoint.x = closest.x;
463 fHoveringCrossDataPoint.y = closest.y;
464 } else {
465 fHoveringCrossDataPoint.x = -1;
466 fHoveringCrossDataPoint.y = -1;
467 }
468 refresh();
469 }
470 }
471
472 private final class LamiScatterMouseDownListener extends MouseAdapter {
473
474 @Override
475 public void mouseDown(@Nullable MouseEvent event) {
476 if (event == null || event.button != 1) {
477 return;
478 }
479
480 int xMouseLocation = event.x;
481 int yMouseLocation = event.y;
482
483 boolean ctrlMode = false;
484
485 ISeries[] series = getChart().getSeriesSet().getSeries();
486 Set<Integer> selections = getSelection();
487
488 /* Check for ctrl on click */
489 if ((event.stateMask & SWT.CTRL) != 0) {
490 selections = getSelection();
491 ctrlMode = true;
492 } else {
493 /* Reset selection */
494 unsetSelection();
495 selections = new HashSet<>();
496 }
497
498 for (ISeries oneSeries : series) {
499 ILineSeries lineSerie = (ILineSeries) oneSeries;
500
501 int closest = -1;
502 double closestDistance = -1;
503 for (int i = 0; i < lineSerie.getXSeries().length; i++) {
504 Point dataPoint = lineSerie.getPixelCoordinates(i);
505
506 /*
507 * Find the distance between the data point and the mouse
508 * location, and compare it to the symbol size so when a
509 * user clicks on a symbol it selects it.
510 */
511 double distance = Math.hypot(dataPoint.x - xMouseLocation, dataPoint.y - yMouseLocation);
512 int snapRangeRadius = lineSerie.getSymbolSize() * SELECTION_SNAP_RANGE_MULTIPLIER;
513 if (distance < snapRangeRadius) {
514 if (closestDistance == -1 || distance < closestDistance) {
515 closest = i;
516 closestDistance = distance;
517 }
518 }
519 }
520 if (closest != -1) {
521 /* Translate to global index */
522 int tableEntryIndex = getTableEntryIndexFromGraphIndex(checkNotNull(oneSeries), closest);
523 if (tableEntryIndex < 0) {
524 continue;
525 }
526 LamiTableEntry entry = getResultTable().getEntries().get(tableEntryIndex);
527 int index = getResultTable().getEntries().indexOf(entry);
528
529 if (!ctrlMode || !selections.remove(index)) {
530 selections.add(index);
531 }
532 /* Do no iterate since we already found a match */
533 break;
534 }
535 }
536 setSelection(selections);
537 /* Signal all Lami viewers & views of the selection */
538 LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(this,
539 selections, checkNotNull(getResultTable().hashCode()));
540 TmfSignalManager.dispatchSignal(signal);
541 refresh();
542 }
543 }
544
545 private final class LamiScatterPainterListener implements PaintListener {
546
547 @Override
548 public void paintControl(@Nullable PaintEvent e) {
549 if (e == null) {
550 return;
551 }
552 GC gc = e.gc;
553
554 /* Draw the selection */
555 drawSelectedDot(checkNotNull(gc));
556
557 /* Draw the hovering cross */
558 drawHoveringCross(checkNotNull(gc));
559 }
560
561 private void drawSelectedDot(GC gc) {
562 if (isSelected()) {
563 Iterator<Color> colorsIt;
564 colorsIt = Iterators.cycle(COLORS);
565 for (ISeries series : getChart().getSeriesSet().getSeries()) {
566
567 /* Get series colors */
568 Color color = colorsIt.next();
569 int symbolSize = ((ILineSeries) series).getSymbolSize();
570
571 for (int index : getInternalSelections()) {
572 int graphIndex = getGraphIndexFromTableEntryIndex(series, index);
573
574 if (graphIndex < 0) {
575 continue;
576 }
577 Point point = series.getPixelCoordinates(graphIndex);
578
579 /* Create a colored dot for selection */
580 gc.setBackground(color);
581 gc.fillOval(point.x - symbolSize, point.y - symbolSize, symbolSize * 2, symbolSize * 2);
582
583 /* Draw cross */
584 gc.setLineWidth(2);
585 gc.setLineStyle(SWT.LINE_SOLID);
586 /* Vertical line */
587 int drawingDelta = SELECTION_CROSS_SIZE_MULTIPLIER * symbolSize;
588 gc.drawLine(point.x, point.y - drawingDelta, point.x, point.y + drawingDelta);
589 /* Horizontal line */
590 gc.drawLine(point.x - drawingDelta, point.y, point.x + drawingDelta, point.y);
591
592 }
593 }
594 }
595 }
596
597 private void drawHoveringCross(GC gc) {
598 gc.setLineWidth(1);
599 gc.setLineStyle(SWT.LINE_SOLID);
600 gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
601 gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
602 /* Vertical line */
603 gc.drawLine(fHoveringCrossDataPoint.x, 0, fHoveringCrossDataPoint.x, getChart().getPlotArea().getSize().y);
604 /* Horizontal line */
605 gc.drawLine(0, fHoveringCrossDataPoint.y, getChart().getPlotArea().getSize().x, fHoveringCrossDataPoint.y);
606 }
607 }
608
609 // ------------------------------------------------------------------------
610 // Utility functions
611 // ------------------------------------------------------------------------
612
613 private int getTableEntryIndexFromGraphIndex(ISeries series, int index) {
614 List<Integer> indexes = fIndexMapping.get(series);
615 if (indexes == null || index > indexes.size() || index < 0) {
616 return -1;
617 }
618 return indexes.get(index);
619 }
620
621 private int getGraphIndexFromTableEntryIndex(ISeries series, int index) {
622 List<Integer> indexes = fIndexMapping.get(series);
623 if (indexes == null || !indexes.contains(index)) {
624 return -1;
625 }
626 return indexes.indexOf(index);
627 }
628
629 @Override
630 protected void refreshDisplayLabels() {
631 }
632
633 /**
634 * Return the current selection in internal mapping
635 *
636 * @return the internal selections
637 */
638 protected Set<Integer> getInternalSelections() {
639 /* Translate to internal table location */
640 Set<Integer> indexes = super.getSelection();
641 Set<Integer> internalIndexes = indexes.stream()
642 .mapToInt(index -> getResultTable().getEntries().indexOf((getResultTable().getEntries().get(index))))
643 .boxed()
644 .collect(Collectors.toSet());
645 return internalIndexes;
646 }
647
648 private static void updateTickMark(BiMap<@Nullable String, Integer> map, IAxisTick tick, int availableLenghtPixel) {
649 int nbLabels = Math.max(1, map.size());
650 int stepSizePixel = availableLenghtPixel / nbLabels;
651 /*
652 * This step is a limitation on swtchart side regarding minimal grid
653 * step hint size. When the step size are smaller it get defined as the
654 * "default" value for the axis instead of the smallest one.
655 */
656 if (IAxisTick.MIN_GRID_STEP_HINT > stepSizePixel) {
657 stepSizePixel = (int) IAxisTick.MIN_GRID_STEP_HINT;
658 }
659 tick.setTickMarkStepHint(stepSizePixel);
660 }
661
662 @Override
663 protected void setSelection(@NonNull Set<@NonNull Integer> selection) {
664 super.setSelection(selection);
665
666 /* Set color of selected symbol */
667 Iterator<Color> colorsIt = Iterators.cycle(COLORS);
668 Iterator<Color> lightColorsIt = Iterators.cycle(LIGHT_COLORS);
669
670 Set<Integer> currentSelections = getInternalSelections();
671
672 for (ISeries series : getChart().getSeriesSet().getSeries()) {
673 /* Series color */
674 Color lightColor = lightColorsIt.next();
675 Color color = colorsIt.next();
676 Color[] colors = ((ILineSeries) series).getSymbolColors();
677
678 if (currentSelections.isEmpty()) {
679 /* Put all symbols to the normal colors */
680 Arrays.fill(colors, color);
681 } else {
682 /*
683 * Fill with light colors to represent the deselected state. The
684 * paint listener is then responsible for drawing the cross and
685 * the dark colors for the selection.
686 */
687 Arrays.fill(colors, lightColor);
688 }
689 ((ILineSeries) series).setSymbolColors(colors);
690 }
691 }
692
693}
This page took 0.055013 seconds and 5 git commands to generate.