1 /*******************************************************************************
2 * Copyright (c) 2015 Ericsson
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
10 * Alexis Cabana-Loriaux - Initial API and implementation
12 *******************************************************************************/
14 package org
.eclipse
.tracecompass
.internal
.tmf
.ui
.viewers
.piecharts
;
16 import java
.util
.ArrayList
;
17 import java
.util
.Collections
;
18 import java
.util
.HashMap
;
19 import java
.util
.List
;
21 import java
.util
.Map
.Entry
;
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
;
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.
40 * This class is closely related with the IPieChartViewerState interface that
41 * acts as a state machine for the general layout of the charts.
43 * @author Alexis Cabana-Loriaux
47 public class TmfPieChartViewer
extends Composite
{
50 * The pie chart containing global information about the trace
52 private PieChart fGlobalPC
;
55 * The name of the piechart containing the statistics about the global trace
57 private String fGlobalPCname
;
60 * The pie chart containing information about the current time-range
63 private PieChart fTimeRangePC
;
66 * The name of the piechart containing the statistics about the current
69 private String fTimeRangePCname
;
72 * The listener for the mouse movement event.
74 private Listener fMouseMoveListener
;
77 * The listener for the mouse right click event.
79 private MouseListener fMouseClickListener
;
82 * The list of listener to notify when an event type is selected
84 private ListenerList fEventTypeSelectedListeners
= new ListenerList(ListenerList
.IDENTITY
);
87 * The name of the slice containing the too little slices
89 private String fOthersSliceName
;
92 * Implementation of the State design pattern to reorder the layout
93 * depending on the selection. This variable holds the current state of the
96 private IPieChartViewerState fCurrentState
;
99 * Represents the minimum percentage a slice of pie must have in order to be
102 private static final float MIN_PRECENTAGE_TO_SHOW_SLICE
= 0.025F
;// 2.5%
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.
108 private static final int NB_MAX_SLICES
= 10;
111 * The data that has to be presented by the pie charts
113 private TmfPieChartStatisticsModel fModel
= null;
117 * The parent composite that will hold the viewer
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
;
127 // ------------------------------------------------------------------------
129 // ------------------------------------------------------------------------
132 * Called by this class' constructor. Constructs the basic viewer containing
133 * the charts, as well as their listeners
135 private void initContent() {
136 setLayout(new FillLayout());
141 // Setup listeners for the tooltips
142 fMouseMoveListener
= new Listener() {
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 */
149 int sliceIndex
= pc
.getSliceIndexFromPosition(0, event
.x
, event
.y
);
150 if (sliceIndex
< 0) {
151 // mouse is outside the chart
152 pc
.setToolTipText(null);
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]);
159 String text
= Messages
.TmfStatisticsView_PieChartToolTipTextName
+ " = " + //$NON-NLS-1$
160 pc
.getSeriesSet().getSeries()[sliceIndex
].getId() + "\n"; //$NON-NLS-1$
162 text
+= Messages
.TmfStatisticsView_PieChartToolTipTextEventCount
+ " = "//$NON-NLS-1$
163 + nbEvents
.toString() + " (" + percent
+ "%)"; //$NON-NLS-1$ //$NON-NLS-2$
164 pc
.setToolTipText(text
);
171 fMouseClickListener
= new MouseListener() {
174 public void mouseUp(MouseEvent e
) {
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
185 Event selectionEvent
= new Event();
186 selectionEvent
.text
= pc
.getSeriesSet().getSeries()[slicenb
].getId();
187 notifyEventTypeSelectionListener(selectionEvent
);
191 public void mouseDoubleClick(MouseEvent e
) {
195 // at creation no content is selected
196 setCurrentState(new PieChartViewerStateNoContentSelected(this));
200 public void dispose() {
201 if (fGlobalPC
!= null) {
204 if (fTimeRangePC
!= null) {
205 fTimeRangePC
.dispose();
211 * Updates the data contained in the Global PieChart by using a Map.
212 * Normally, this method is only called by the state machine.
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) {
227 Map
<String
, Long
> totalEventCountForChart
= getTotalEventCountForChart(true);
229 if (totalEventCountForChart
== null) {
233 updatePieChartWithData(fGlobalPC
, totalEventCountForChart
, MIN_PRECENTAGE_TO_SHOW_SLICE
, fOthersSliceName
);
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.
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
);
250 else if (getTimeRangePC().isDisposed()) {
254 Map
<String
, Long
> totalEventCountForChart
= getTotalEventCountForChart(false);
256 if (totalEventCountForChart
== null) {
260 updatePieChartWithData(fTimeRangePC
, totalEventCountForChart
, MIN_PRECENTAGE_TO_SHOW_SLICE
, fOthersSliceName
);
263 /* return the chart-friendly map given by the TmfPieChartStatisticsModel */
264 private Map
<String
, Long
> getTotalEventCountForChart(boolean isGlobal
) {
265 if (fModel
== null) {
268 Map
<ITmfTrace
, Map
<String
, Long
>> chartModel
;
270 chartModel
= fModel
.getPieChartGlobalModel();
272 chartModel
= fModel
.getPieChartSelectionModel();
274 if (chartModel
== null) {
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) {
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());
288 totalEventCountForChart
.put(event
.getKey(), event
.getValue());
293 return totalEventCountForChart
;
297 * Reinitializes the charts to their initial state, without any data
299 synchronized public void reinitializeCharts() {
304 if (getGlobalPC() != null && !getGlobalPC().isDisposed()) {
305 getGlobalPC().dispose();
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();
315 setCurrentState(new PieChartViewerStateNoContentSelected(this));
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
323 private static void updatePieChartWithData(
324 final PieChart chart
,
325 final Map
<String
, Long
> slices
,
326 final float minimumSizeOfSlice
,
327 final String nameOfOthers
) {
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()));
336 // No events in the selection
337 if (eventTotal
== 0) {
338 // clear the chart and show "NO DATA"
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
347 List
<EventOccurrenceObject
> filteredChartValues
= new ArrayList
<>();
348 Long othersEntryCount
= 0L;
350 for (EventOccurrenceObject entry
: chartValues
) {
351 if (entry
.getNbOccurence() / eventTotal
.floatValue() > minimumSizeOfSlice
&& nbSlices
<= NB_MAX_SLICES
) {
352 filteredChartValues
.add(entry
);
355 othersEntryCount
+= entry
.getNbOccurence();
359 Collections
.sort(filteredChartValues
);
361 // Add the "Others" slice in the pie if its not empty
362 if (othersEntryCount
!= 0) {
363 filteredChartValues
.add(new EventOccurrenceObject(nameOfOthers
, othersEntryCount
));
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()];
370 for (EventOccurrenceObject entry
: filteredChartValues
) {
371 tempValues
[index
][0] = entry
.getNbOccurence();
372 tempNames
[index
] = entry
.getName();
376 chart
.addPieChartSeries(tempNames
, tempValues
);
380 * Refresh this viewer
382 * @param refreshGlobal
383 * if we have to refresh the global piechart
384 * @param refreshSelection
385 * if we have to refresh the selection piechart
387 public synchronized void refresh(boolean refreshGlobal
, boolean refreshSelection
) {
388 if (fModel
== null) {
389 reinitializeCharts();
392 /* will update the global pc */
393 getCurrentState().newGlobalEntries(this);
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()) {
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);
411 getCurrentState().newSelection(this);
419 * the listener to add
421 public void addEventTypeSelectionListener(Listener l
) {
422 fEventTypeSelectedListeners
.add(l
);
427 * the listener to remove
429 public void removeEventTypeSelectionListener(Listener l
) {
430 fEventTypeSelectedListeners
.remove(l
);
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
);
440 // ------------------------------------------------------------------------
442 // ------------------------------------------------------------------------
445 * @return the global piechart
447 synchronized PieChart
getGlobalPC() {
452 * @return the time-range selection piechart
454 synchronized PieChart
getTimeRangePC() {
459 * @return the current state of the viewer
461 synchronized IPieChartViewerState
getCurrentState() {
462 return fCurrentState
;
465 // ------------------------------------------------------------------------
467 // ------------------------------------------------------------------------
472 public TmfPieChartStatisticsModel
getModel() {
480 public void setInput(TmfPieChartStatisticsModel model
) {
485 * Normally, this method is only called by the state machine
490 public synchronized void setTimeRangePC(PieChart newChart
) {
491 fTimeRangePC
= newChart
;
495 * Setter method for the state.
498 * The new state of the viewer Normally only called by classes
499 * implementing the IPieChartViewerState interface.
501 public synchronized void setCurrentState(final IPieChartViewerState newState
) {
502 fCurrentState
= newState
;
506 * Nested class used to handle and sort more easily the pair (Name, Number
509 * @author Alexis Cabana-Loriaux
511 private static class EventOccurrenceObject
implements Comparable
<EventOccurrenceObject
> {
513 private String fName
;
515 private Long fNbOccurrences
;
517 EventOccurrenceObject(String name
, Long nbOccurences
) {
519 this.fNbOccurrences
= nbOccurences
;
523 public int compareTo(EventOccurrenceObject other
) {
525 return -Long
.compare(this.getNbOccurence(), other
.getNbOccurence());
528 public String
getName() {
532 public Long
getNbOccurence() {
533 return fNbOccurrences
;