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