1 /*******************************************************************************
2 * Copyright (c) 2016 EfficiOS Inc., Jonathan Rajotte-Julien
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *******************************************************************************/
10 package org
.eclipse
.tracecompass
.internal
.provisional
.analysis
.lami
.ui
.viewers
;
12 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
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
;
24 import java
.util
.TreeSet
;
25 import java
.util
.stream
.Collectors
;
26 import java
.util
.stream
.Stream
;
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
;
58 import com
.google
.common
.collect
.BiMap
;
59 import com
.google
.common
.collect
.HashBiMap
;
60 import com
.google
.common
.collect
.Iterators
;
63 * XY Scatter chart viewer for Lami views
65 * @author Jonathan Rajotte-Julien
67 public class LamiScatterViewer
extends LamiXYChartViewer
{
69 private static final int SELECTION_SNAP_RANGE_MULTIPLIER
= 20;
70 private static final int SELECTION_CROSS_SIZE_MULTIPLIER
= 3;
72 private final Map
<ISeries
, List
<Integer
>> fIndexMapping
;
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
));
78 private @Nullable LamiGraphRange fXExternalRange
= null;
79 private @Nullable LamiGraphRange fYExternalRange
= null;
81 /* The current data point for the hovering cross */
82 private Point fHoveringCrossDataPoint
;
90 * Result table populating this chart
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$
100 /* Inspect X series */
101 fIndexMapping
= new HashMap
<>();
103 fHoveringCrossDataPoint
= new Point(-1, -1);
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
);
112 BiMap
<@Nullable String
, Integer
> xMap
= checkNotNull(HashBiMap
.create());
113 boolean xIsLog
= graphModel
.xAxisIsLog();
115 boolean areXAspectsContinuous
= areAspectsContinuous(xAxisAspects
);
116 boolean areXAspectsTimeStamp
= areAspectsTimeStamp(xAxisAspects
);
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$
123 if (aspect
.isTimeStamp() != areXAspectsTimeStamp
) {
124 throw new IllegalStateException("Some X aspects are time based and some are not"); //$NON-NLS-1$
129 * When xAxisAspects are discrete create a map for all values of all
132 if (!areXAspectsContinuous
) {
133 generateLabelMap(xAxisAspects
, checkNotNull(xMap
));
136 * Always clamp the range to min and max
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
142 fXExternalRange
= getRange(xAxisAspects
, false);
148 List
<LamiTableEntryAspect
> yAxisAspects
= getYAxisAspects();
149 BiMap
<@Nullable String
, Integer
> yMap
= checkNotNull(HashBiMap
.create());
150 boolean yIsLog
= graphModel
.yAxisIsLog();
152 boolean areYAspectsContinuous
= areAspectsContinuous(yAxisAspects
);
153 boolean areYAspectsTimeStamp
= areAspectsTimeStamp(yAxisAspects
);
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$
160 if (aspect
.isTimeStamp() != areYAspectsTimeStamp
) {
161 throw new IllegalStateException("Some Y aspects are time based and some are not"); //$NON-NLS-1$
166 * When yAspects are discrete create a map for all values of all series
168 if (!areYAspectsContinuous
) {
169 generateLabelMap(yAxisAspects
, yMap
);
172 * Only clamp the range to the minimum value if it is a time stamp since
173 * plotting from 1970 would make little sense.
175 fYExternalRange
= getRange(yAxisAspects
, areYAspectsTimeStamp
);
178 /* Plot the series */
180 for (LamiTableEntryAspect yAspect
: getYAxisAspects()) {
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();
188 xAspect
= xAxisAspects
.get(index
);
189 name
= (yAspect
.getName() + ' ' + Messages
.LamiScatterViewer_by
+ ' ' + xAspect
.getName());
192 List
<@Nullable Double
> xDoubleSeries
;
193 List
<@Nullable Double
> yDoubleSeries
;
195 if (xAspect
.isContinuous()) {
196 xDoubleSeries
= getResultTable().getEntries().stream()
198 Number number
= xAspect
.resolveNumber(entry
);
199 if (number
!= null && fXExternalRange
!= null) {
200 return getInternalDoubleValue(number
, fXInternalRange
, fXExternalRange
);
204 .collect(Collectors
.toList());
206 xDoubleSeries
= getResultTable().getEntries().stream()
208 String string
= xAspect
.resolveString(entry
);
209 Integer value
= xMap
.get(string
);
211 return Double
.valueOf(value
.doubleValue());
215 .collect(Collectors
.toList());
218 if (yAspect
.isContinuous()) {
219 yDoubleSeries
= getResultTable().getEntries().stream()
221 Number number
= yAspect
.resolveNumber(entry
);
222 if (number
!= null && fYExternalRange
!= null) {
223 return getInternalDoubleValue(number
, fYInternalRange
, fYExternalRange
);
227 .collect(Collectors
.toList());
229 yDoubleSeries
= getResultTable().getEntries().stream()
231 String string
= yAspect
.resolveString(entry
);
232 Integer value
= yMap
.get(string
);
234 return Double
.valueOf(value
.doubleValue());
238 .collect(Collectors
.toList());
241 List
<@Nullable Double
> validXDoubleSeries
= new ArrayList
<>();
242 List
<@Nullable Double
> validYDoubleSeries
= new ArrayList
<>();
243 List
<Integer
> indexSeriesCorrespondance
= new ArrayList
<>();
245 if (xDoubleSeries
.size() != yDoubleSeries
.size()) {
246 throw new IllegalStateException("Series sizes don't match!"); //$NON-NLS-1$
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 */
257 if ((xIsLog
&& xValue
<= ZERO_DOUBLE
) || (yIsLog
&& yValue
<= ZERO_DOUBLE
)) {
259 * Equal or less than 0 values can't be plotted on log scale
263 validXDoubleSeries
.add(xValue
);
264 validYDoubleSeries
.add(yValue
);
265 indexSeriesCorrespondance
.add(i
);
268 if (validXDoubleSeries
.isEmpty() || validXDoubleSeries
.isEmpty()) {
269 /* No need to plot an empty series */
274 ILineSeries scatterSeries
= (ILineSeries
) getChart().getSeriesSet().createSeries(SeriesType
.LINE
, name
);
275 scatterSeries
.setLineStyle(LineStyle
.NONE
);
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
);
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
);
290 xTick
.setFormat(xAxisFormat
);
292 if (xAxisFormat
instanceof LamiTimeStampFormat
) {
293 setXUnits(((LamiTimeStampFormat
) xAxisFormat
).getPattern());
296 xTick
.setFormat(new LamiLabelFormat(checkNotNull(xMap
)));
297 updateTickMark(checkNotNull(xMap
), xTick
, getChart().getPlotArea().getSize().x
);
299 /* Remove vertical grid line */
300 getChart().getAxisSet().getXAxis(0).getGrid().setStyle(LineStyle
.NONE
);
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
);
308 yTick
.setFormat(yAxisFormat
);
310 if (yAxisFormat
instanceof LamiTimeStampFormat
) {
311 setYUnits(((LamiTimeStampFormat
) yAxisFormat
).getPattern());
314 yTick
.setFormat(new LamiLabelFormat(checkNotNull(yMap
)));
315 updateTickMark(checkNotNull(yMap
), yTick
, getChart().getPlotArea().getSize().y
);
317 /* Remove horizontal grid line */
318 getChart().getAxisSet().getYAxis(0).getGrid().setStyle(LineStyle
.NONE
);
322 * SWTChart workaround: SWTChart fiddles with tick mark visibility based
323 * on the fact that it can parse the label to double or not.
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.
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.
334 yTick
.setTickLabelAngle(1);
336 setLineSeriesColor();
338 /* Put log scale if necessary */
339 if (xIsLog
&& areXAspectsContinuous
&& !areXAspectsTimeStamp
) {
340 Stream
.of(getChart().getAxisSet().getXAxes()).forEach(axis
-> axis
.enableLogScale(xIsLog
));
343 if (yIsLog
&& areYAspectsContinuous
&& !areYAspectsTimeStamp
) {
344 /* Set the axis as logscale */
345 Stream
.of(getChart().getAxisSet().getYAxes()).forEach(axis
-> axis
.enableLogScale(yIsLog
));
347 getChart().getAxisSet().adjustRange();
352 getChart().getPlotArea().addMouseListener(new LamiScatterMouseDownListener());
355 * Hovering cross listener
357 getChart().getPlotArea().addMouseMoveListener(new HoveringCrossListener());
360 * Mouse exit listener: reset state of hovering cross on mouse exit.
362 getChart().getPlotArea().addListener(SWT
.MouseExit
, new Listener() {
365 public void handleEvent(@Nullable Event event
) {
367 fHoveringCrossDataPoint
.x
= -1;
368 fHoveringCrossDataPoint
.y
= -1;
375 * Selections and hovering cross painting
377 getChart().getPlotArea().addPaintListener(new LamiScatterPainterListener());
379 /* On resize check for axis tick updating */
380 getChart().addListener(SWT
.Resize
, new Listener() {
382 public void handleEvent(@Nullable Event event
) {
383 if (yTick
.getFormat() instanceof LamiLabelFormat
) {
384 updateTickMark(checkNotNull(yMap
), yTick
, getChart().getPlotArea().getSize().y
);
386 if (xTick
.getFormat() instanceof LamiLabelFormat
) {
387 updateTickMark(checkNotNull(xMap
), xTick
, getChart().getPlotArea().getSize().x
);
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) {
403 /* Ordered label mapping to double */
404 for (String string
: set
) {
405 map
.put(string
, map
.size());
410 * Set the chart series colors.
412 private void setLineSeriesColor() {
413 Iterator
<Color
> colorsIt
;
415 colorsIt
= Iterators
.cycle(COLORS
);
417 for (ISeries series
: getChart().getSeriesSet().getSeries()) {
418 ((ILineSeries
) series
).setSymbolColor((colorsIt
.next()));
420 * Generate initial array of Color to enable per point color change
421 * on selection in the future
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
));
428 ((ILineSeries
) series
).setSymbolColors(colors
.toArray(new Color
[colors
.size()]));
432 // ------------------------------------------------------------------------
434 // ------------------------------------------------------------------------
436 private final class HoveringCrossListener
implements MouseMoveListener
{
439 public void mouseMove(@Nullable MouseEvent e
) {
443 ISeries
[] series
= getChart().getSeriesSet().getSeries();
444 @Nullable Point closest
= null;
445 double closestDistance
= -1.0;
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
);
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.
458 int snapRangeRadius
= lineSerie
.getSymbolSize() * SELECTION_SNAP_RANGE_MULTIPLIER
;
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.
466 double distance
= Math
.hypot(dataPoint
.x
- e
.x
, dataPoint
.y
- e
.y
);
467 if (distance
< snapRangeRadius
) {
468 if (closestDistance
== -1 || distance
< closestDistance
) {
470 closestDistance
= distance
;
475 if (closest
!= null) {
476 fHoveringCrossDataPoint
.x
= closest
.x
;
477 fHoveringCrossDataPoint
.y
= closest
.y
;
479 fHoveringCrossDataPoint
.x
= -1;
480 fHoveringCrossDataPoint
.y
= -1;
486 private final class LamiScatterMouseDownListener
extends MouseAdapter
{
489 public void mouseDown(@Nullable MouseEvent event
) {
490 if (event
== null || event
.button
!= 1) {
494 int xMouseLocation
= event
.x
;
495 int yMouseLocation
= event
.y
;
497 boolean ctrlMode
= false;
499 ISeries
[] series
= getChart().getSeriesSet().getSeries();
500 Set
<Integer
> selections
= getSelection();
502 /* Check for ctrl on click */
503 if ((event
.stateMask
& SWT
.CTRL
) != 0) {
504 selections
= getSelection();
507 /* Reset selection */
509 selections
= new HashSet
<>();
512 for (ISeries oneSeries
: series
) {
513 ILineSeries lineSerie
= (ILineSeries
) oneSeries
;
516 double closestDistance
= -1;
517 for (int i
= 0; i
< lineSerie
.getXSeries().length
; i
++) {
518 Point dataPoint
= lineSerie
.getPixelCoordinates(i
);
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.
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
) {
530 closestDistance
= distance
;
535 /* Translate to global index */
536 int tableEntryIndex
= getTableEntryIndexFromGraphIndex(checkNotNull(oneSeries
), closest
);
537 if (tableEntryIndex
< 0) {
540 LamiTableEntry entry
= getResultTable().getEntries().get(tableEntryIndex
);
541 int index
= getResultTable().getEntries().indexOf(entry
);
543 if (!ctrlMode
|| !selections
.remove(index
)) {
544 selections
.add(index
);
546 /* Do no iterate since we already found a match */
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
);
559 private final class LamiScatterPainterListener
implements PaintListener
{
562 public void paintControl(@Nullable PaintEvent e
) {
568 /* Draw the selection */
569 drawSelectedDot(checkNotNull(gc
));
571 /* Draw the hovering cross */
572 drawHoveringCross(checkNotNull(gc
));
575 private void drawSelectedDot(GC gc
) {
577 Iterator
<Color
> colorsIt
;
578 colorsIt
= Iterators
.cycle(COLORS
);
579 for (ISeries series
: getChart().getSeriesSet().getSeries()) {
581 /* Get series colors */
582 Color color
= colorsIt
.next();
583 int symbolSize
= ((ILineSeries
) series
).getSymbolSize();
585 for (int index
: getInternalSelections()) {
586 int graphIndex
= getGraphIndexFromTableEntryIndex(series
, index
);
588 if (graphIndex
< 0) {
591 Point point
= series
.getPixelCoordinates(graphIndex
);
593 /* Create a colored dot for selection */
594 gc
.setBackground(color
);
595 gc
.fillOval(point
.x
- symbolSize
, point
.y
- symbolSize
, symbolSize
* 2, symbolSize
* 2);
599 gc
.setLineStyle(SWT
.LINE_SOLID
);
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
);
611 private void drawHoveringCross(GC gc
) {
613 gc
.setLineStyle(SWT
.LINE_SOLID
);
614 gc
.setForeground(Display
.getCurrent().getSystemColor(SWT
.COLOR_BLACK
));
615 gc
.setBackground(Display
.getCurrent().getSystemColor(SWT
.COLOR_WHITE
));
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
);
623 // ------------------------------------------------------------------------
625 // ------------------------------------------------------------------------
627 private int getTableEntryIndexFromGraphIndex(ISeries series
, int index
) {
628 List
<Integer
> indexes
= fIndexMapping
.get(series
);
629 if (indexes
== null || index
> indexes
.size() || index
< 0) {
632 return indexes
.get(index
);
635 private int getGraphIndexFromTableEntryIndex(ISeries series
, int index
) {
636 List
<Integer
> indexes
= fIndexMapping
.get(series
);
637 if (indexes
== null || !indexes
.contains(index
)) {
640 return indexes
.indexOf(index
);
644 protected void refreshDisplayLabels() {
648 * Return the current selection in internal mapping
650 * @return the internal selections
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
))))
658 .collect(Collectors
.toSet());
659 return internalIndexes
;
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
;
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.
670 if (IAxisTick
.MIN_GRID_STEP_HINT
> stepSizePixel
) {
671 stepSizePixel
= (int) IAxisTick
.MIN_GRID_STEP_HINT
;
673 tick
.setTickMarkStepHint(stepSizePixel
);
677 protected void setSelection(@NonNull Set
<@NonNull Integer
> selection
) {
678 super.setSelection(selection
);
680 /* Set color of selected symbol */
681 Iterator
<Color
> colorsIt
= Iterators
.cycle(COLORS
);
682 Iterator
<Color
> lightColorsIt
= Iterators
.cycle(LIGHT_COLORS
);
684 Set
<Integer
> currentSelections
= getInternalSelections();
686 for (ISeries series
: getChart().getSeriesSet().getSeries()) {
688 Color lightColor
= lightColorsIt
.next();
689 Color color
= colorsIt
.next();
690 Color
[] colors
= ((ILineSeries
) series
).getSymbolColors();
692 if (currentSelections
.isEmpty()) {
693 /* Put all symbols to the normal colors */
694 Arrays
.fill(colors
, color
);
697 * Fill with light colors to represent the deselected state. The
698 * paint listener is then responsible for drawing the cross and
699 * the dark colors for the selection.
701 Arrays
.fill(colors
, lightColor
);
703 ((ILineSeries
) series
).setSymbolColors(colors
);