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