|
1 /* |
|
2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 package com.nokia.s60tools.swmtanalyser.ui.graphs; |
|
18 |
|
19 import java.util.Arrays; |
|
20 import java.util.List; |
|
21 import java.util.Random; |
|
22 |
|
23 import org.eclipse.draw2d.Graphics; |
|
24 import org.eclipse.swt.SWT; |
|
25 import org.eclipse.swt.graphics.Color; |
|
26 import org.eclipse.swt.graphics.Font; |
|
27 import org.eclipse.swt.graphics.GC; |
|
28 import org.eclipse.swt.graphics.Image; |
|
29 import org.eclipse.swt.graphics.ImageData; |
|
30 import org.eclipse.swt.graphics.ImageLoader; |
|
31 import org.eclipse.swt.graphics.Point; |
|
32 import org.eclipse.swt.widgets.Composite; |
|
33 import org.eclipse.swt.widgets.Display; |
|
34 import org.eclipse.swt.widgets.FileDialog; |
|
35 |
|
36 import com.nokia.s60tools.swmtanalyser.ui.graphs.GenericGraph.EventTypes; |
|
37 import com.nokia.s60tools.util.debug.DbgUtility; |
|
38 |
|
39 /** |
|
40 * Graph utilities common to all graph types. |
|
41 */ |
|
42 public class GraphsUtils { |
|
43 |
|
44 // |
|
45 // Constants |
|
46 // |
|
47 |
|
48 /** |
|
49 * Constant for no marker possibility. |
|
50 */ |
|
51 private static final int NO_MARKER = -1; |
|
52 |
|
53 /** |
|
54 * Possible marker sizes from smallest to biggest value. |
|
55 */ |
|
56 private static final int[] MARKER_SIZES = { 4, 6, 8 }; |
|
57 |
|
58 /** |
|
59 * Default marker size. |
|
60 */ |
|
61 private static final int DEFAULT_MARKER = MARKER_SIZES[2]; |
|
62 |
|
63 /** |
|
64 * Array of event names used to map the event names into corresponding event |
|
65 * enumerators. |
|
66 * |
|
67 * @see com.nokia.s60tools.swmtanalyser.ui.graphs.GenericGraph.EventTypes |
|
68 */ |
|
69 private static final String[] EVENT_NAMES_ARR = { "Global data size", |
|
70 "Non heap chunk size", "Disk used", "Disk total", "No of Files", |
|
71 "Max size", "Heap size", "Heap allocated space", "Heap free space", |
|
72 "Heap allocated cell count", "Heap free cell count", "Free slack", |
|
73 "No of PS Handles", "RAM used", "RAM total", "System Data" }; |
|
74 |
|
75 /** |
|
76 * Percentage of byte count to be graphed that is added extra reserve in |
|
77 * order to show markers appropriately. |
|
78 */ |
|
79 private static final int MARKER_MARGIN_PERCENTAGE = 5; |
|
80 |
|
81 // |
|
82 // Symbolic name constants for different byte units use in setting Y-axis |
|
83 // value level according given maximum byte value |
|
84 // |
|
85 private static final int KILOBYTE = 1024; |
|
86 private static final int MEGABYTE = 1024 * 1024; |
|
87 |
|
88 // |
|
89 // Members |
|
90 // |
|
91 private static String imageFilename; |
|
92 private static Composite parentComposite; |
|
93 |
|
94 /** |
|
95 * Maps event names into corresponding enumerator type |
|
96 * |
|
97 * @param event |
|
98 * event name |
|
99 * @return enumerator constant corresponding to the given event |
|
100 */ |
|
101 public static EventTypes getMappedEvent(String eventName) { |
|
102 int index = Arrays.asList(EVENT_NAMES_ARR).indexOf(eventName); |
|
103 GenericGraph.EventTypes eventType = null; |
|
104 |
|
105 switch (index) { |
|
106 case 0: |
|
107 eventType = GenericGraph.EventTypes.GLOBAL_DATA_SIZE; |
|
108 break; |
|
109 case 1: |
|
110 eventType = GenericGraph.EventTypes.NON_HEAP_CHUNK_SIZE; |
|
111 break; |
|
112 case 2: |
|
113 eventType = GenericGraph.EventTypes.DISK_USED_SIZE; |
|
114 break; |
|
115 case 3: |
|
116 eventType = GenericGraph.EventTypes.DISK_TOTAL_SIZE; |
|
117 break; |
|
118 case 4: |
|
119 eventType = GenericGraph.EventTypes.NO_OF_FILES; |
|
120 break; |
|
121 case 5: |
|
122 eventType = GenericGraph.EventTypes.MAX_HEAP_SIZE; |
|
123 break; |
|
124 case 6: |
|
125 eventType = GenericGraph.EventTypes.HEAP_SIZE; |
|
126 break; |
|
127 case 7: |
|
128 eventType = GenericGraph.EventTypes.HEAP_ALLOC_SPACE; |
|
129 break; |
|
130 case 8: |
|
131 eventType = GenericGraph.EventTypes.HEAP_FREE_SPACE; |
|
132 break; |
|
133 case 9: |
|
134 eventType = GenericGraph.EventTypes.HEAP_ALLOC_CELL_COUNT; |
|
135 break; |
|
136 case 10: |
|
137 eventType = GenericGraph.EventTypes.HEAP_FREE_CELL_COUNT; |
|
138 break; |
|
139 case 11: |
|
140 eventType = GenericGraph.EventTypes.HEAP_FREE_SLACK; |
|
141 break; |
|
142 case 12: |
|
143 eventType = GenericGraph.EventTypes.NO_OF_PSHANDLES; |
|
144 break; |
|
145 case 13: |
|
146 eventType = GenericGraph.EventTypes.RAM_USED; |
|
147 break; |
|
148 case 14: |
|
149 eventType = GenericGraph.EventTypes.RAM_TOTAL; |
|
150 break; |
|
151 case 15: |
|
152 eventType = GenericGraph.EventTypes.SYSTEM_DATA; |
|
153 break; |
|
154 } |
|
155 |
|
156 return eventType; |
|
157 } |
|
158 |
|
159 /** |
|
160 * Get next scale when zooming in or out |
|
161 * @param scale |
|
162 * @param bigger give <code>true</code> when zooming out and <code>false</code> when zooming in. |
|
163 * @return next scale |
|
164 */ |
|
165 public static double nextScale(double scale, boolean bigger) { |
|
166 double logScale = Math.log10(scale); |
|
167 double floorLogScale = Math.floor(Math.log10(scale)); |
|
168 double mostSignificantDigit = Math.rint(Math.pow(10, |
|
169 (logScale - floorLogScale))); |
|
170 double powerOfTen = Math.pow(10, floorLogScale); |
|
171 |
|
172 if (bigger) { |
|
173 if (mostSignificantDigit < 2) { |
|
174 mostSignificantDigit = 2; |
|
175 } else if (mostSignificantDigit < 5) { |
|
176 mostSignificantDigit = 5; |
|
177 } else { |
|
178 mostSignificantDigit = 10; |
|
179 } |
|
180 } else { |
|
181 if (mostSignificantDigit > 5) { |
|
182 mostSignificantDigit = 5; |
|
183 } else if (mostSignificantDigit > 2) { |
|
184 mostSignificantDigit = 2; |
|
185 } else if (mostSignificantDigit > 1) { |
|
186 mostSignificantDigit = 1; |
|
187 } else { |
|
188 mostSignificantDigit = 0.5; |
|
189 } |
|
190 } |
|
191 |
|
192 double result = mostSignificantDigit * powerOfTen; |
|
193 |
|
194 if (result < 0.1) |
|
195 result = 0.1; |
|
196 |
|
197 return result; |
|
198 } |
|
199 |
|
200 /** |
|
201 * Save the given composite as an image to local file system. |
|
202 * |
|
203 * @param parent |
|
204 */ |
|
205 public static void saveGraph(Composite parent) { |
|
206 parentComposite = parent; |
|
207 FileDialog dlg = new FileDialog(Display.getCurrent().getActiveShell(), |
|
208 SWT.SAVE); |
|
209 dlg.setFilterExtensions(new String[] { "*.bmp", "*.png", "*.jpeg" }); |
|
210 imageFilename = dlg.open(); |
|
211 if (imageFilename == null) |
|
212 return; |
|
213 |
|
214 Runnable p = new Runnable() { |
|
215 public void run() { |
|
216 GC gc = new GC(parentComposite); |
|
217 Image image = new Image(Display.getCurrent(), parentComposite |
|
218 .getClientArea().width, |
|
219 parentComposite.getClientArea().height); |
|
220 parentComposite.setFocus(); |
|
221 gc.copyArea(image, 0, 0); |
|
222 gc.dispose(); |
|
223 ImageData data = image.getImageData(); |
|
224 ImageLoader loader = new ImageLoader(); |
|
225 loader.data = new ImageData[] { data }; |
|
226 if (imageFilename != null) |
|
227 loader.save(imageFilename, SWT.IMAGE_BMP); |
|
228 image.dispose(); |
|
229 } |
|
230 }; |
|
231 Display.getDefault().timerExec(500, p); |
|
232 } |
|
233 |
|
234 /** |
|
235 * Generate random color. |
|
236 * |
|
237 * @return a random color |
|
238 */ |
|
239 public static Color getRandomColor() { |
|
240 Random rand = new Random(); |
|
241 int r = rand.nextInt(255); |
|
242 int g = rand.nextInt(255); |
|
243 int b = rand.nextInt(255); |
|
244 return new Color(Display.getCurrent(), r, g, b); |
|
245 } |
|
246 |
|
247 /** |
|
248 * Creates an image and writes the given text vertically on the image. This |
|
249 * is used to represent the Y-axis names in the Analysis tab and Graphed |
|
250 * events -graphs. Those graphs show double Y-axis and therefore layout |
|
251 * differs from single Y-axis situation. |
|
252 * |
|
253 * @param axisLabelName |
|
254 * name of the label |
|
255 * @return vertical axis label image |
|
256 */ |
|
257 public static Image getDoubleYAxisVerticalLabel(String axisLabelName) { |
|
258 return getVerticalLabel(axisLabelName, 90, 18, 10); |
|
259 } |
|
260 |
|
261 /** |
|
262 * Creates an image and writes the given text vertically on the image with |
|
263 * given coordinates and font size. |
|
264 * |
|
265 * @param axisLabelName |
|
266 * name of the label |
|
267 * @param x |
|
268 * x-coordinate |
|
269 * @param y |
|
270 * y-coordinate |
|
271 * @param fontSize |
|
272 * font size |
|
273 * @return vertical axis label image |
|
274 */ |
|
275 public static Image getVerticalLabel(String axisLabelName, int x, int y, |
|
276 int fontSize) { |
|
277 final Image image = new Image(Display.getDefault(), x, y); |
|
278 GC gc = new GC(image); |
|
279 Font font = new Font(Display.getDefault(), Display.getDefault() |
|
280 .getSystemFont().getFontData()[0].getName(), fontSize, SWT.BOLD); |
|
281 gc.setFont(font); |
|
282 gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); |
|
283 gc.fillRectangle(0, 0, 90, 15); |
|
284 gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); |
|
285 gc.drawText(axisLabelName + " ->", 0, 0, true); |
|
286 font.dispose(); |
|
287 gc.dispose(); |
|
288 return image; |
|
289 } |
|
290 |
|
291 /** |
|
292 * Draws markers to data points with current foreground color and marker |
|
293 * size. |
|
294 * |
|
295 * @param graphics |
|
296 * Graphics context |
|
297 * @param points |
|
298 * Data point array in format [ X0, Y0, X1, Y1, ... ] |
|
299 */ |
|
300 private static void drawMarkers(Graphics graphics, int[] points, |
|
301 int markerSize) { |
|
302 Color backgroundColor = graphics.getBackgroundColor(); |
|
303 graphics.setBackgroundColor(graphics.getForegroundColor()); |
|
304 for (int j = 0; j < points.length; j += 2) { |
|
305 int width = markerSize; |
|
306 int height = width; |
|
307 int x = points[j] - (width / 2); |
|
308 int y = points[j + 1] - (width / 2); |
|
309 graphics.fillRectangle(x, y, width, height); |
|
310 } |
|
311 graphics.setBackgroundColor(backgroundColor); |
|
312 } |
|
313 |
|
314 /** |
|
315 * Resolving minimum space between subsequent X-coordinate data points. This |
|
316 * method expects that there are at least 2 x,y pairs in the array |
|
317 * |
|
318 * @param points |
|
319 * Data point array in format [ X0, Y0, X1, Y1, ... ] |
|
320 * @return minimum space between subsequent X-coordinate data points |
|
321 */ |
|
322 private static int resolveMinumumXDelta(int[] points) { |
|
323 int minDelta = points[2] - points[0]; |
|
324 for (int i = 4; i < points.length; i += 2) { |
|
325 int delta = points[i] - points[i - 2]; |
|
326 if (delta < minDelta) { |
|
327 minDelta = delta; |
|
328 } |
|
329 } |
|
330 return minDelta; |
|
331 } |
|
332 |
|
333 /** |
|
334 * Draws markers to data points with current foreground color. Markers drawn |
|
335 * only if there is enough room in X-axis to draw them between all |
|
336 * individual data points. |
|
337 * |
|
338 * @param graphics |
|
339 * Graphics context |
|
340 * @param points |
|
341 * Data point array in format [ X0, Y0, X1, Y1, ... ] |
|
342 */ |
|
343 public static void drawMarkers(Graphics graphics, int[] points) { |
|
344 int markerSize = NO_MARKER; // By default not drawing markers if there |
|
345 // is no room for them |
|
346 // Checking deltas only if there is more than single point to draw |
|
347 if (points.length > 2) { |
|
348 // Resolving minimum space between subsequent X-coordinate data |
|
349 // points |
|
350 int minDelta = resolveMinumumXDelta(points); |
|
351 // Resolving if there is at all need to draw markers |
|
352 for (int i = MARKER_SIZES.length - 1; i >= 0; i--) { |
|
353 int size = MARKER_SIZES[i]; |
|
354 if (size < minDelta) { |
|
355 markerSize = size; // Using this size in drawing |
|
356 break; |
|
357 } |
|
358 } |
|
359 // DbgUtility.println(DbgUtility.PRIORITY_LOOP, "minDelta: " + minDelta); //$NON-NLS-1$ |
|
360 } else { |
|
361 // Using default marker in case there |
|
362 markerSize = DEFAULT_MARKER; |
|
363 } |
|
364 // DbgUtility.println(DbgUtility.PRIORITY_LOOP, "markerSize: " + markerSize); //$NON-NLS-1$ |
|
365 // Drawing markers |
|
366 if (markerSize != NO_MARKER) { |
|
367 drawMarkers(graphics, points, markerSize); |
|
368 } |
|
369 } |
|
370 |
|
371 /** |
|
372 * Gets nearest Y-legend bytes label from the given bytes |
|
373 * |
|
374 * @param bytes |
|
375 * bytes number |
|
376 * @return nearest Y-legend bytes label from the given bytes |
|
377 */ |
|
378 static public int prettyMaxBytes(int bytes) { |
|
379 |
|
380 // Adding some margin that makes possible to show also markers |
|
381 int byteMarginForMarkers = (int) Math |
|
382 .ceil((MARKER_MARGIN_PERCENTAGE / 100.0) * bytes); |
|
383 bytes = bytes + byteMarginForMarkers; |
|
384 |
|
385 // DbgUtility.println(DbgUtility.PRIORITY_LOOP, "prettyMaxBytes/bytes: " + bytes); //$NON-NLS-1$ |
|
386 // DbgUtility.println(DbgUtility.PRIORITY_LOOP, "byteMarginForMarkers: " + byteMarginForMarkers); //$NON-NLS-1$ |
|
387 |
|
388 // Before 10 KB limit using byte units are used Y-axis legend and |
|
389 // therefore thousand is used as checkpoint limit instead if KILOBYTE |
|
390 final int thousand = 1000; |
|
391 |
|
392 if (bytes < thousand) |
|
393 bytes = thousand; |
|
394 else if (bytes < 10 * thousand) |
|
395 bytes = 10 * thousand; |
|
396 else if (bytes < 20 * KILOBYTE) |
|
397 bytes = 20 * KILOBYTE; |
|
398 else if (bytes < 30 * KILOBYTE) |
|
399 bytes = 30 * KILOBYTE; |
|
400 else if (bytes < 50 * KILOBYTE) |
|
401 bytes = 50 * KILOBYTE; |
|
402 else if (bytes < 100 * KILOBYTE) |
|
403 bytes = 100 * KILOBYTE; |
|
404 else if (bytes < 150 * KILOBYTE) |
|
405 bytes = 150 * KILOBYTE; |
|
406 else if (bytes < 200 * KILOBYTE) |
|
407 bytes = 200 * KILOBYTE; |
|
408 else if (bytes < 300 * KILOBYTE) |
|
409 bytes = 300 * KILOBYTE; |
|
410 else if (bytes < 400 * KILOBYTE) |
|
411 bytes = 400 * KILOBYTE; |
|
412 else if (bytes < 500 * KILOBYTE) |
|
413 bytes = 500 * KILOBYTE; |
|
414 else if (bytes < 600 * KILOBYTE) |
|
415 bytes = 600 * KILOBYTE; |
|
416 else if (bytes < 700 * KILOBYTE) |
|
417 bytes = 700 * KILOBYTE; |
|
418 else if (bytes < 800 * KILOBYTE) |
|
419 bytes = 800 * KILOBYTE; |
|
420 else if (bytes < 900 * KILOBYTE) |
|
421 bytes = 900 * KILOBYTE; |
|
422 else if (bytes < 1000 * KILOBYTE) |
|
423 bytes = 1000 * KILOBYTE; |
|
424 else if (bytes < 1 * MEGABYTE) |
|
425 bytes = 1 * MEGABYTE; |
|
426 else if (bytes < 2 * MEGABYTE) |
|
427 bytes = 2 * MEGABYTE; |
|
428 else if (bytes < 3 * MEGABYTE) |
|
429 bytes = 3 * MEGABYTE; |
|
430 else if (bytes < 5 * MEGABYTE) |
|
431 bytes = 5 * MEGABYTE; |
|
432 else if (bytes < 10 * MEGABYTE) |
|
433 bytes = 10 * MEGABYTE; |
|
434 else if (bytes < 20 * MEGABYTE) |
|
435 bytes = 20 * MEGABYTE; |
|
436 else if (bytes < 30 * MEGABYTE) |
|
437 bytes = 30 * MEGABYTE; |
|
438 else if (bytes < 50 * MEGABYTE) |
|
439 bytes = 50 * MEGABYTE; |
|
440 else if (bytes < 100 * MEGABYTE) |
|
441 bytes = 100 * MEGABYTE; |
|
442 else if (bytes < 200 * MEGABYTE) |
|
443 bytes = 200 * MEGABYTE; |
|
444 else if (bytes < 300 * MEGABYTE) |
|
445 bytes = 300 * MEGABYTE; |
|
446 else if (bytes < 500 * MEGABYTE) |
|
447 bytes = 500 * MEGABYTE; |
|
448 else |
|
449 bytes = ((bytes + 1024 * MEGABYTE - 1) / (1024 * MEGABYTE)) |
|
450 * (1024 * MEGABYTE); |
|
451 |
|
452 return bytes; |
|
453 } |
|
454 |
|
455 /** |
|
456 * Converts list of <code>Point</code> objects into integer array. |
|
457 * |
|
458 * @param pointsList |
|
459 * list of point objects |
|
460 * @return points converted in 1-dimensional integer array. |
|
461 */ |
|
462 public static int[] convertPointListToIntArray(List<Point> pointsList) { |
|
463 int[] integerArray = new int[pointsList.size() * 2]; |
|
464 for (int i = 0, j = 0; i < pointsList.size(); i++, j += 2) { |
|
465 Point pnt = pointsList.get(i); |
|
466 integerArray[j] = pnt.x; |
|
467 integerArray[(j + 1)] = pnt.y; |
|
468 } |
|
469 return integerArray; |
|
470 } |
|
471 |
|
472 /** |
|
473 * Gets nearest Y-legend count value label from the given input count value. |
|
474 * Maximum count value handled is 999*100 i.e. 99900 counts. |
|
475 * |
|
476 * @param inputCountValue |
|
477 * bytes input count value |
|
478 * @return nearest Y-legend count value label from the given input count |
|
479 * value. |
|
480 * @return |
|
481 */ |
|
482 static public int roundToNearestNumber(int inputCountValue) { |
|
483 int tempCount = inputCountValue; |
|
484 |
|
485 // Adding some safe margin for making sure that all data points with |
|
486 // markers are drawn appropriately |
|
487 int countMarginForMarkers = (int) Math |
|
488 .ceil((MARKER_MARGIN_PERCENTAGE / 100.0) * tempCount); |
|
489 tempCount = tempCount + countMarginForMarkers; |
|
490 |
|
491 DbgUtility.println(DbgUtility.PRIORITY_OPERATION, |
|
492 "roundToNearestNumber/count: " + tempCount); //$NON-NLS-1$ |
|
493 DbgUtility.println(DbgUtility.PRIORITY_OPERATION, |
|
494 "countMarginForMarkers: " + countMarginForMarkers); //$NON-NLS-1$ |
|
495 |
|
496 if (tempCount < 10) |
|
497 tempCount = 10; |
|
498 else if (tempCount < 50) |
|
499 tempCount = 50; |
|
500 else if (tempCount < 100) |
|
501 tempCount = 100; |
|
502 else { |
|
503 for (int i = 2; i < 1000; i++) { |
|
504 if (tempCount < (i * 100)) { |
|
505 tempCount = i * 100; |
|
506 break; |
|
507 } |
|
508 } |
|
509 } |
|
510 |
|
511 return tempCount; |
|
512 } |
|
513 |
|
514 /** |
|
515 * Builds int array from Integer List object |
|
516 * |
|
517 * @param solidsList |
|
518 * Integer list |
|
519 * @return int array |
|
520 */ |
|
521 public static int[] CreateIntArrayFromIntegerList(List<Integer> solidsList) { |
|
522 int[] solidPts = new int[solidsList.size()]; |
|
523 for (int j = 0; j < solidsList.size(); j++) { |
|
524 solidPts[j] = solidsList.get(j); |
|
525 } |
|
526 return solidPts; |
|
527 } |
|
528 |
|
529 } |