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