1 /*******************************************************************************
2 * Copyright (c) 2015, 2016 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
.internal
.tmf
.ui
.viewers
.piecharts
.model
.TmfPieChartStatisticsModel
;
33 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.ITmfTrace
;
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 synchronized 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 * Updates the data contained in the Global PieChart by using a Map.
201 * Normally, this method is only called by the state machine.
203 synchronized void updateGlobalPieChart() {
204 if (getGlobalPC() == null) {
205 fGlobalPC
= new PieChart(this, SWT
.NONE
);
206 getGlobalPC().getTitle().setText(fGlobalPCname
);
207 getGlobalPC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
208 getGlobalPC().getLegend().setVisible(true);
209 getGlobalPC().getLegend().setPosition(SWT
.RIGHT
);
210 getGlobalPC().addListener(SWT
.MouseMove
, fMouseMoveListener
);
211 getGlobalPC().addMouseListener(fMouseClickListener
);
212 } else if (getGlobalPC().isDisposed() || fModel
== null || fModel
.getPieChartGlobalModel() == null) {
216 Map
<String
, Long
> totalEventCountForChart
= getTotalEventCountForChart(true);
218 if (totalEventCountForChart
== null) {
222 updatePieChartWithData(fGlobalPC
, totalEventCountForChart
, MIN_PRECENTAGE_TO_SHOW_SLICE
, fOthersSliceName
);
226 * Updates the data contained in the Time-Range PieChart by using a Map.
227 * Normally, this method is only called by the state machine.
229 synchronized void updateTimeRangeSelectionPieChart() {
230 if (getTimeRangePC() == null) {
231 fTimeRangePC
= new PieChart(this, SWT
.NONE
);
232 getTimeRangePC().getTitle().setText(fTimeRangePCname
);
233 getTimeRangePC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
234 getTimeRangePC().getLegend().setPosition(SWT
.BOTTOM
);
235 getTimeRangePC().getLegend().setVisible(true);
236 getTimeRangePC().addListener(SWT
.MouseMove
, fMouseMoveListener
);
237 getTimeRangePC().addMouseListener(fMouseClickListener
);
239 else if (getTimeRangePC().isDisposed()) {
243 Map
<String
, Long
> totalEventCountForChart
= getTotalEventCountForChart(false);
245 if (totalEventCountForChart
== null) {
249 updatePieChartWithData(fTimeRangePC
, totalEventCountForChart
, MIN_PRECENTAGE_TO_SHOW_SLICE
, fOthersSliceName
);
252 /* return the chart-friendly map given by the TmfPieChartStatisticsModel */
253 private Map
<String
, Long
> getTotalEventCountForChart(boolean isGlobal
) {
254 if (fModel
== null) {
257 Map
<ITmfTrace
, Map
<String
, Long
>> chartModel
;
259 chartModel
= fModel
.getPieChartGlobalModel();
261 chartModel
= fModel
.getPieChartSelectionModel();
263 if (chartModel
== null) {
267 Map
<String
, Long
> totalEventCountForChart
= new HashMap
<>();
268 for (Entry
<ITmfTrace
, Map
<String
, Long
>> entry
: chartModel
.entrySet()) {
269 Map
<String
, Long
> traceEventCount
= entry
.getValue();
270 if (traceEventCount
== null) {
273 for (Entry
<String
, Long
> event
: traceEventCount
.entrySet()) {
274 final Long value
= totalEventCountForChart
.get(event
.getKey());
276 totalEventCountForChart
.put(event
.getKey(), value
+ event
.getValue());
278 totalEventCountForChart
.put(event
.getKey(), event
.getValue());
283 return totalEventCountForChart
;
287 * Reinitializes the charts to their initial state, without any data
289 public synchronized void reinitializeCharts() {
294 if (getGlobalPC() != null && !getGlobalPC().isDisposed()) {
295 getGlobalPC().dispose();
297 fGlobalPC
= new PieChart(this, SWT
.NONE
);
298 getGlobalPC().getTitle().setText(fGlobalPCname
);
299 getGlobalPC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
300 if (getTimeRangePC() != null && !getTimeRangePC().isDisposed()) {
301 getTimeRangePC().dispose();
305 setCurrentState(new PieChartViewerStateNoContentSelected(this));
309 * Function used to update or create the slices of a PieChart to match the
310 * content of a Map passed in parameter. It also provides a facade to use
313 private static void updatePieChartWithData(
314 final PieChart chart
,
315 final Map
<String
, Long
> slices
,
316 final float minimumSizeOfSlice
,
317 final String nameOfOthers
) {
319 List
<EventOccurrenceObject
> chartValues
= new ArrayList
<>();
320 Long eventTotal
= 0L;
321 for (Entry
<String
, Long
> entry
: slices
.entrySet()) {
322 eventTotal
+= entry
.getValue();
323 chartValues
.add(new EventOccurrenceObject(entry
.getKey(), entry
.getValue()));
326 // No events in the selection
327 if (eventTotal
== 0) {
328 // clear the chart and show "NO DATA"
334 * filter out the event types taking too little space in the chart and
335 * label the whole group together. The remaining slices will be showing
337 List
<EventOccurrenceObject
> filteredChartValues
= new ArrayList
<>();
338 Long othersEntryCount
= 0L;
340 for (EventOccurrenceObject entry
: chartValues
) {
341 if (entry
.getNbOccurence() / eventTotal
.floatValue() > minimumSizeOfSlice
&& nbSlices
<= NB_MAX_SLICES
) {
342 filteredChartValues
.add(entry
);
345 othersEntryCount
+= entry
.getNbOccurence();
349 Collections
.sort(filteredChartValues
);
351 // Add the "Others" slice in the pie if its not empty
352 if (othersEntryCount
!= 0) {
353 filteredChartValues
.add(new EventOccurrenceObject(nameOfOthers
, othersEntryCount
));
356 // put the entries in the chart and add their percentage
357 double[][] tempValues
= new double[filteredChartValues
.size()][1];
358 String
[] tempNames
= new String
[filteredChartValues
.size()];
360 for (EventOccurrenceObject entry
: filteredChartValues
) {
361 tempValues
[index
][0] = entry
.getNbOccurence();
362 tempNames
[index
] = entry
.getName();
366 chart
.addPieChartSeries(tempNames
, tempValues
);
370 * Refresh this viewer
372 * @param refreshGlobal
373 * if we have to refresh the global piechart
374 * @param refreshSelection
375 * if we have to refresh the selection piechart
377 public synchronized void refresh(boolean refreshGlobal
, boolean refreshSelection
) {
378 if (fModel
== null) {
379 reinitializeCharts();
382 /* will update the global pc */
383 getCurrentState().newGlobalEntries(this);
386 if (refreshSelection
) {
387 // Check if the selection is empty
388 int nbEventsType
= 0;
389 Map
<String
, Long
> selectionModel
= getTotalEventCountForChart(false);
390 for (Long l
: selectionModel
.values()) {
396 // Check if the selection is empty or if
397 // there is enough event types to show in the piecharts
398 if (nbEventsType
< 2) {
399 getCurrentState().newEmptySelection(this);
401 getCurrentState().newSelection(this);
409 * the listener to add
411 public void addEventTypeSelectionListener(Listener l
) {
412 fEventTypeSelectedListeners
.add(l
);
417 * the listener to remove
419 public void removeEventTypeSelectionListener(Listener l
) {
420 fEventTypeSelectedListeners
.remove(l
);
423 /* Notify all listeners that an event type has been selected */
424 private void notifyEventTypeSelectionListener(Event e
) {
425 for (Object o
: fEventTypeSelectedListeners
.getListeners()) {
426 ((Listener
) o
).handleEvent(e
);
430 // ------------------------------------------------------------------------
432 // ------------------------------------------------------------------------
435 * @return the global piechart
437 synchronized PieChart
getGlobalPC() {
442 * @return the time-range selection piechart
444 synchronized PieChart
getTimeRangePC() {
449 * @return the current state of the viewer
451 synchronized IPieChartViewerState
getCurrentState() {
452 return fCurrentState
;
455 // ------------------------------------------------------------------------
457 // ------------------------------------------------------------------------
462 public TmfPieChartStatisticsModel
getModel() {
470 public void setInput(TmfPieChartStatisticsModel model
) {
475 * Normally, this method is only called by the state machine
480 public synchronized void setTimeRangePC(PieChart newChart
) {
481 fTimeRangePC
= newChart
;
485 * Setter method for the state.
488 * The new state of the viewer Normally only called by classes
489 * implementing the IPieChartViewerState interface.
491 public synchronized void setCurrentState(final IPieChartViewerState newState
) {
492 fCurrentState
= newState
;
496 * Nested class used to handle and sort more easily the pair (Name, Number
499 * @author Alexis Cabana-Loriaux
501 private static class EventOccurrenceObject
implements Comparable
<EventOccurrenceObject
> {
503 private String fName
;
505 private Long fNbOccurrences
;
507 EventOccurrenceObject(String name
, Long nbOccurences
) {
509 this.fNbOccurrences
= nbOccurences
;
513 public int compareTo(EventOccurrenceObject other
) {
515 return Long
.compare(other
.getNbOccurence(), this.getNbOccurence());
518 public String
getName() {
522 public Long
getNbOccurence() {
523 return fNbOccurrences
;