tmf : Redefine CompareTo() in EventOccurrenceObject
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / internal / tmf / ui / viewers / piecharts / TmfPieChartViewer.java
1 /*******************************************************************************
2 * Copyright (c) 2015 Ericsson
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 * Contributors:
10 * Alexis Cabana-Loriaux - Initial API and implementation
11 *
12 *******************************************************************************/
13
14 package org.eclipse.tracecompass.internal.tmf.ui.viewers.piecharts;
15
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22
23 import org.eclipse.core.runtime.ListenerList;
24 import org.eclipse.linuxtools.dataviewers.piechart.PieChart;
25 import org.eclipse.swt.SWT;
26 import org.eclipse.swt.events.MouseEvent;
27 import org.eclipse.swt.events.MouseListener;
28 import org.eclipse.swt.layout.FillLayout;
29 import org.eclipse.swt.widgets.Composite;
30 import org.eclipse.swt.widgets.Event;
31 import org.eclipse.swt.widgets.Listener;
32 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
33 import org.eclipse.tracecompass.internal.tmf.ui.viewers.piecharts.model.TmfPieChartStatisticsModel;
34
35 /**
36 * Creates a viewer containing 2 pie charts, one for showing information about
37 * the current selection, and the second one for showing information about the
38 * current time-range selection. It follows the MVC pattern, being a view.
39 *
40 * This class is closely related with the IPieChartViewerState interface that
41 * acts as a state machine for the general layout of the charts.
42 *
43 * @author Alexis Cabana-Loriaux
44 * @since 2.0
45 *
46 */
47 public class TmfPieChartViewer extends Composite {
48
49 /**
50 * The pie chart containing global information about the trace
51 */
52 private PieChart fGlobalPC;
53
54 /**
55 * The name of the piechart containing the statistics about the global trace
56 */
57 private String fGlobalPCname;
58
59 /**
60 * The pie chart containing information about the current time-range
61 * selection
62 */
63 private PieChart fTimeRangePC;
64
65 /**
66 * The name of the piechart containing the statistics about the current
67 * selection
68 */
69 private String fTimeRangePCname;
70
71 /**
72 * The listener for the mouse movement event.
73 */
74 private Listener fMouseMoveListener;
75
76 /**
77 * The listener for the mouse right click event.
78 */
79 private MouseListener fMouseClickListener;
80
81 /**
82 * The list of listener to notify when an event type is selected
83 */
84 private ListenerList fEventTypeSelectedListeners = new ListenerList(ListenerList.IDENTITY);
85
86 /**
87 * The name of the slice containing the too little slices
88 */
89 private String fOthersSliceName;
90
91 /**
92 * Implementation of the State design pattern to reorder the layout
93 * depending on the selection. This variable holds the current state of the
94 * layout.
95 */
96 private IPieChartViewerState fCurrentState;
97
98 /**
99 * Represents the minimum percentage a slice of pie must have in order to be
100 * shown
101 */
102 private static final float MIN_PRECENTAGE_TO_SHOW_SLICE = 0.025F;// 2.5%
103
104 /**
105 * Represents the maximum number of slices of the pie charts. WE don't want
106 * to pollute the viewer with too much slice entries.
107 */
108 private static final int NB_MAX_SLICES = 10;
109
110 /**
111 * The data that has to be presented by the pie charts
112 */
113 private TmfPieChartStatisticsModel fModel = null;
114
115 /**
116 * @param parent
117 * The parent composite that will hold the viewer
118 */
119 public TmfPieChartViewer(Composite parent) {
120 super(parent, SWT.NONE);
121 fGlobalPCname = Messages.TmfStatisticsView_GlobalSelectionPieChartName;
122 fTimeRangePCname = Messages.TmfStatisticsView_TimeRangeSelectionPieChartName;
123 fOthersSliceName = Messages.TmfStatisticsView_PieChartOthersSliceName;
124 initContent();
125 }
126
127 // ------------------------------------------------------------------------
128 // Class methods
129 // ------------------------------------------------------------------------
130
131 /**
132 * Called by this class' constructor. Constructs the basic viewer containing
133 * the charts, as well as their listeners
134 */
135 private void initContent() {
136 setLayout(new FillLayout());
137
138 fGlobalPC = null;
139 fTimeRangePC = null;
140
141 // Setup listeners for the tooltips
142 fMouseMoveListener = new Listener() {
143 @Override
144 public void handleEvent(org.eclipse.swt.widgets.Event event) {
145 PieChart pc = (PieChart) event.widget;
146 switch (event.type) {
147 /* Get tooltip information on the slice */
148 case SWT.MouseMove:
149 int sliceIndex = pc.getSliceIndexFromPosition(0, event.x, event.y);
150 if (sliceIndex < 0) {
151 // mouse is outside the chart
152 pc.setToolTipText(null);
153 break;
154 }
155 float percOfSlice = (float) pc.getSlicePercent(0, sliceIndex);
156 String percent = String.format("%.1f", percOfSlice); //$NON-NLS-1$
157 Long nbEvents = Long.valueOf((long) pc.getSeriesSet().getSeries()[sliceIndex].getXSeries()[0]);
158
159 String text = Messages.TmfStatisticsView_PieChartToolTipTextName + " = " + //$NON-NLS-1$
160 pc.getSeriesSet().getSeries()[sliceIndex].getId() + "\n"; //$NON-NLS-1$
161
162 text += Messages.TmfStatisticsView_PieChartToolTipTextEventCount + " = "//$NON-NLS-1$
163 + nbEvents.toString() + " (" + percent + "%)"; //$NON-NLS-1$ //$NON-NLS-2$
164 pc.setToolTipText(text);
165 return;
166 default:
167 }
168 }
169 };
170
171 fMouseClickListener = new MouseListener() {
172
173 @Override
174 public void mouseUp(MouseEvent e) {
175 }
176
177 @Override
178 public void mouseDown(MouseEvent e) {
179 PieChart pc = (PieChart) e.widget;
180 int slicenb = pc.getSliceIndexFromPosition(0, e.x, e.y);
181 if (slicenb < 0 || slicenb >= pc.getSeriesSet().getSeries().length) {
182 // mouse is outside the chart
183 return;
184 }
185 Event selectionEvent = new Event();
186 selectionEvent.text = pc.getSeriesSet().getSeries()[slicenb].getId();
187 notifyEventTypeSelectionListener(selectionEvent);
188 }
189
190 @Override
191 public void mouseDoubleClick(MouseEvent e) {
192 }
193 };
194
195 // at creation no content is selected
196 setCurrentState(new PieChartViewerStateNoContentSelected(this));
197 }
198
199 @Override
200 public void dispose() {
201 if (fGlobalPC != null) {
202 fGlobalPC.dispose();
203 }
204 if (fTimeRangePC != null) {
205 fTimeRangePC.dispose();
206 }
207 super.dispose();
208 }
209
210 /**
211 * Updates the data contained in the Global PieChart by using a Map.
212 * Normally, this method is only called by the state machine.
213 */
214 synchronized void updateGlobalPieChart() {
215 if (getGlobalPC() == null) {
216 fGlobalPC = new PieChart(this, SWT.NONE);
217 getGlobalPC().getTitle().setText(fGlobalPCname);
218 getGlobalPC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
219 getGlobalPC().getLegend().setVisible(true);
220 getGlobalPC().getLegend().setPosition(SWT.RIGHT);
221 getGlobalPC().addListener(SWT.MouseMove, fMouseMoveListener);
222 getGlobalPC().addMouseListener(fMouseClickListener);
223 } else if (getGlobalPC().isDisposed() || fModel == null || fModel.getPieChartGlobalModel() == null) {
224 return;
225 }
226
227 Map<String, Long> totalEventCountForChart = getTotalEventCountForChart(true);
228
229 if (totalEventCountForChart == null) {
230 return;
231 }
232
233 updatePieChartWithData(fGlobalPC, totalEventCountForChart, MIN_PRECENTAGE_TO_SHOW_SLICE, fOthersSliceName);
234 }
235
236 /**
237 * Updates the data contained in the Time-Range PieChart by using a Map.
238 * Normally, this method is only called by the state machine.
239 */
240 synchronized void updateTimeRangeSelectionPieChart() {
241 if (getTimeRangePC() == null) {
242 fTimeRangePC = new PieChart(this, SWT.NONE);
243 getTimeRangePC().getTitle().setText(fTimeRangePCname);
244 getTimeRangePC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
245 getTimeRangePC().getLegend().setPosition(SWT.BOTTOM);
246 getTimeRangePC().getLegend().setVisible(true);
247 getTimeRangePC().addListener(SWT.MouseMove, fMouseMoveListener);
248 getTimeRangePC().addMouseListener(fMouseClickListener);
249 }
250 else if (getTimeRangePC().isDisposed()) {
251 return;
252 }
253
254 Map<String, Long> totalEventCountForChart = getTotalEventCountForChart(false);
255
256 if (totalEventCountForChart == null) {
257 return;
258 }
259
260 updatePieChartWithData(fTimeRangePC, totalEventCountForChart, MIN_PRECENTAGE_TO_SHOW_SLICE, fOthersSliceName);
261 }
262
263 /* return the chart-friendly map given by the TmfPieChartStatisticsModel */
264 private Map<String, Long> getTotalEventCountForChart(boolean isGlobal) {
265 if (fModel == null) {
266 return null;
267 }
268 Map<ITmfTrace, Map<String, Long>> chartModel;
269 if (isGlobal) {
270 chartModel = fModel.getPieChartGlobalModel();
271 } else {
272 chartModel = fModel.getPieChartSelectionModel();
273 }
274 if (chartModel == null) {
275 return null;
276 }
277
278 Map<String, Long> totalEventCountForChart = new HashMap<>();
279 for (Entry<ITmfTrace, Map<String, Long>> entry : chartModel.entrySet()) {
280 Map<String, Long> traceEventCount = entry.getValue();
281 if (traceEventCount == null) {
282 continue;
283 }
284 for (Entry<String, Long> event : traceEventCount.entrySet()) {
285 if (totalEventCountForChart.containsKey(event.getKey())) {
286 totalEventCountForChart.put(event.getKey(), totalEventCountForChart.get(event.getKey()) + event.getValue());
287 } else {
288 totalEventCountForChart.put(event.getKey(), event.getValue());
289 }
290 }
291 }
292
293 return totalEventCountForChart;
294 }
295
296 /**
297 * Reinitializes the charts to their initial state, without any data
298 */
299 synchronized public void reinitializeCharts() {
300 if (isDisposed()) {
301 return;
302 }
303
304 if (getGlobalPC() != null && !getGlobalPC().isDisposed()) {
305 getGlobalPC().dispose();
306 }
307 fGlobalPC = new PieChart(this, SWT.NONE);
308 getGlobalPC().getTitle().setText(fGlobalPCname);
309 getGlobalPC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
310 if (getTimeRangePC() != null && !getTimeRangePC().isDisposed()) {
311 getTimeRangePC().dispose();
312 fTimeRangePC = null;
313 }
314 layout();
315 setCurrentState(new PieChartViewerStateNoContentSelected(this));
316 }
317
318 /**
319 * Function used to update or create the slices of a PieChart to match the
320 * content of a Map passed in parameter. It also provides a facade to use
321 * the PieChart API
322 */
323 private static void updatePieChartWithData(
324 final PieChart chart,
325 final Map<String, Long> slices,
326 final float minimumSizeOfSlice,
327 final String nameOfOthers) {
328
329 List<EventOccurrenceObject> chartValues = new ArrayList<>();
330 Long eventTotal = 0L;
331 for (Entry<String, Long> entry : slices.entrySet()) {
332 eventTotal += entry.getValue();
333 chartValues.add(new EventOccurrenceObject(entry.getKey(), entry.getValue()));
334 }
335
336 // No events in the selection
337 if (eventTotal == 0) {
338 // clear the chart and show "NO DATA"
339
340 return;
341 }
342
343 /*
344 * filter out the event types taking too little space in the chart and
345 * label the whole group together. The remaining slices will be showing
346 */
347 List<EventOccurrenceObject> filteredChartValues = new ArrayList<>();
348 Long othersEntryCount = 0L;
349 int nbSlices = 0;
350 for (EventOccurrenceObject entry : chartValues) {
351 if (entry.getNbOccurence() / eventTotal.floatValue() > minimumSizeOfSlice && nbSlices <= NB_MAX_SLICES) {
352 filteredChartValues.add(entry);
353 nbSlices++;
354 } else {
355 othersEntryCount += entry.getNbOccurence();
356 }
357 }
358
359 Collections.sort(filteredChartValues);
360
361 // Add the "Others" slice in the pie if its not empty
362 if (othersEntryCount != 0) {
363 filteredChartValues.add(new EventOccurrenceObject(nameOfOthers, othersEntryCount));
364 }
365
366 // put the entries in the chart and add their percentage
367 double[][] tempValues = new double[filteredChartValues.size()][1];
368 String[] tempNames = new String[filteredChartValues.size()];
369 int index = 0;
370 for (EventOccurrenceObject entry : filteredChartValues) {
371 tempValues[index][0] = entry.getNbOccurence();
372 tempNames[index] = entry.getName();
373 index++;
374 }
375
376 chart.addPieChartSeries(tempNames, tempValues);
377 }
378
379 /**
380 * Refresh this viewer
381 *
382 * @param refreshGlobal
383 * if we have to refresh the global piechart
384 * @param refreshSelection
385 * if we have to refresh the selection piechart
386 */
387 public synchronized void refresh(boolean refreshGlobal, boolean refreshSelection) {
388 if (fModel == null) {
389 reinitializeCharts();
390 } else {
391 if (refreshGlobal) {
392 /* will update the global pc */
393 getCurrentState().newGlobalEntries(this);
394 }
395
396 if (refreshSelection) {
397 // Check if the selection is empty
398 int nbEventsType = 0;
399 Map<String, Long> selectionModel = getTotalEventCountForChart(false);
400 for (Long l : selectionModel.values()) {
401 if (l != 0) {
402 nbEventsType++;
403 }
404 }
405
406 // Check if the selection is empty or if
407 // there is enough event types to show in the piecharts
408 if (nbEventsType < 2) {
409 getCurrentState().newEmptySelection(this);
410 } else {
411 getCurrentState().newSelection(this);
412 }
413 }
414 }
415 }
416
417 /**
418 * @param l
419 * the listener to add
420 */
421 public void addEventTypeSelectionListener(Listener l) {
422 fEventTypeSelectedListeners.add(l);
423 }
424
425 /**
426 * @param l
427 * the listener to remove
428 */
429 public void removeEventTypeSelectionListener(Listener l) {
430 fEventTypeSelectedListeners.remove(l);
431 }
432
433 /* Notify all listeners that an event type has been selected */
434 private void notifyEventTypeSelectionListener(Event e) {
435 for (Object o : fEventTypeSelectedListeners.getListeners()) {
436 ((Listener) o).handleEvent(e);
437 }
438 }
439
440 // ------------------------------------------------------------------------
441 // Getters
442 // ------------------------------------------------------------------------
443
444 /**
445 * @return the global piechart
446 */
447 synchronized PieChart getGlobalPC() {
448 return fGlobalPC;
449 }
450
451 /**
452 * @return the time-range selection piechart
453 */
454 synchronized PieChart getTimeRangePC() {
455 return fTimeRangePC;
456 }
457
458 /**
459 * @return the current state of the viewer
460 */
461 synchronized IPieChartViewerState getCurrentState() {
462 return fCurrentState;
463 }
464
465 // ------------------------------------------------------------------------
466 // Setters
467 // ------------------------------------------------------------------------
468
469 /**
470 * @return the model
471 */
472 public TmfPieChartStatisticsModel getModel() {
473 return fModel;
474 }
475
476 /**
477 * @param model
478 * the model to set
479 */
480 public void setInput(TmfPieChartStatisticsModel model) {
481 fModel = model;
482 }
483
484 /**
485 * Normally, this method is only called by the state machine
486 *
487 * @param newChart
488 * the new PieChart
489 */
490 public synchronized void setTimeRangePC(PieChart newChart) {
491 fTimeRangePC = newChart;
492 }
493
494 /**
495 * Setter method for the state.
496 *
497 * @param newState
498 * The new state of the viewer Normally only called by classes
499 * implementing the IPieChartViewerState interface.
500 */
501 public synchronized void setCurrentState(final IPieChartViewerState newState) {
502 fCurrentState = newState;
503 }
504
505 /**
506 * Nested class used to handle and sort more easily the pair (Name, Number
507 * of occurrences)
508 *
509 * @author Alexis Cabana-Loriaux
510 */
511 private static class EventOccurrenceObject implements Comparable<EventOccurrenceObject> {
512
513 private String fName;
514
515 private Long fNbOccurrences;
516
517 EventOccurrenceObject(String name, Long nbOccurences) {
518 this.fName = name;
519 this.fNbOccurrences = nbOccurences;
520 }
521
522 @Override
523 public int compareTo(EventOccurrenceObject other) {
524 // descending order
525 return Long.compare(other.getNbOccurence(), this.getNbOccurence());
526 }
527
528 public String getName() {
529 return fName;
530 }
531
532 public Long getNbOccurence() {
533 return fNbOccurrences;
534 }
535 }
536 }
This page took 0.060184 seconds and 6 git commands to generate.