View Javadoc

1   /* OpenLogViewer
2    *
3    * Copyright 2011
4    *
5    * This file is part of the OpenLogViewer project.
6    *
7    * OpenLogViewer software is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation, either version 3 of the License, or
10   * (at your option) any later version.
11   *
12   * OpenLogViewer software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with any OpenLogViewer software.  If not, see http://www.gnu.org/licenses/
19   *
20   * I ask that if you make any changes to this file you fork the code on github.com!
21   *
22   */
23  package org.diyefi.openlogviewer.graphing;
24  
25  import java.awt.Color;
26  import java.awt.Graphics;
27  import java.awt.Graphics2D;
28  import java.awt.event.HierarchyBoundsListener;
29  import java.awt.event.HierarchyEvent;
30  import java.beans.PropertyChangeEvent;
31  import java.beans.PropertyChangeListener;
32  import java.text.DecimalFormatSymbols;
33  
34  import javax.swing.JPanel;
35  
36  import org.diyefi.openlogviewer.Keys;
37  import org.diyefi.openlogviewer.OpenLogViewer;
38  import org.diyefi.openlogviewer.genericlog.GenericDataElement;
39  import org.diyefi.openlogviewer.utils.MathUtils;
40  
41  /**
42   * SingleGraphPanel is a JPanel that uses a transparent background.
43   * The graph trace is drawn to this panel and used in conjunction with a JLayeredPane
44   * to give the appearance of all the graph traces drawn together.
45   *
46   * This layer listens for window resizes and property changes.
47   * @author Bryan Harris and Ben Fenner
48   */
49  public class SingleGraphPanel extends JPanel implements HierarchyBoundsListener, PropertyChangeListener {
50  	public static final int DECIMAL_PLACES = 3;
51  
52  	private static final long serialVersionUID = 1L;
53  
54  	private static final int MINIMUM_MARGIN_PIXELS = 3;
55  	private static final int SHOW_DATA_POINT_ZOOM_THRESHOLD = 5;
56  	private static final int DATA_POINT_WIDTH = 3;
57  	private static final int DATA_POINT_HEIGHT = DATA_POINT_WIDTH;
58  	private static final float GRAPH_TRACE_HEIGHT_AS_PERCENTAGE_OF_TOTAL_TRACK_HEIGHT = 0.94F;
59  	private static final char DS = DecimalFormatSymbols.getInstance().getDecimalSeparator();
60  	private static final String UNKNOWN_DIGIT = "-"; // String, not char, so we can append below
61  	private static final String UNKNOWN_NUMBER = UNKNOWN_DIGIT + DS + UNKNOWN_DIGIT;
62  	private static final String STAT_SEPARATOR = " | ";
63  	private static final String UNKNOWN_STATS = UNKNOWN_NUMBER + STAT_SEPARATOR + UNKNOWN_NUMBER + STAT_SEPARATOR + UNKNOWN_NUMBER;
64  
65  	private GenericDataElement gde;
66  	private double[] dataPointsToDisplay;
67  	private double[][] dataPointRangeInfo;
68  	private int availableDataRecords;
69  	private int graphBeginningIndex;
70  	private int graphEndingIndex;
71  	private int graphTraceMinHeight;
72  	private int graphTraceMaxHeight;
73  
74  
75  	public SingleGraphPanel() {
76  		setOpaque(false);
77  		setLayout(null);
78  
79  		graphBeginningIndex = Integer.MIN_VALUE;
80  		graphEndingIndex = Integer.MIN_VALUE;
81  	}
82  
83  	@Override
84  	public void ancestorMoved(final HierarchyEvent e) {
85  	}
86  
87  	@Override
88  	public final void ancestorResized(final HierarchyEvent e) {
89  		if (e.getID() == HierarchyEvent.ANCESTOR_RESIZED) {
90  			sizeGraph();
91  		}
92  	}
93  
94  	@Override
95  	public final void propertyChange(final PropertyChangeEvent evt) {
96  		if (Keys.SPLIT.equals(evt.getPropertyName())) {
97  			sizeGraph();
98  		}
99  	}
100 
101 	@Override
102 	public final void paintComponent(final Graphics g) {
103 		super.paintComponent(g);
104 
105 		if (!this.getSize().equals(this.getParent().getSize())) {
106 			this.setSize(this.getParent().getSize());
107 		}
108 
109 		initGraph();
110 
111 		if (hasDataPointToDisplay()) {
112 			paintDataPointsAndTraces(g);
113 		}
114 	}
115 
116 	private void paintDataPointsAndTraces(final Graphics g) {
117 		// Setup graphics stuff
118 		final Graphics2D g2d = (Graphics2D) g;
119 		g2d.setColor(gde.getDisplayColor());
120 
121 		// Initialize current, previous and next graph trace data points
122 		double leftOfTraceData = -Double.MAX_VALUE;
123 		double traceData = -Double.MAX_VALUE;
124 		double rightOfTraceData = dataPointsToDisplay[0];
125 
126 		// Initialize designated visible graph trace status markers
127 		boolean atTraceBeginning = false;
128 		boolean insideTrace = false;
129 		boolean atTraceEnd = false;
130 
131 		// Initialize and setup data point screen location stuff
132 		final boolean zoomedOut = OpenLogViewer.getInstance().getEntireGraphingPanel().isZoomedOutBeyondOneToOne();
133 		int distanceBetweenDataPoints = OpenLogViewer.getInstance().getEntireGraphingPanel().getZoom();
134 		if (zoomedOut) {
135 			distanceBetweenDataPoints = 1;
136 		}
137 		final double graphPosition = OpenLogViewer.getInstance().getEntireGraphingPanel().getGraphPosition();
138 		final double offset = (graphPosition % 1) * distanceBetweenDataPoints;
139 		int screenPositionXCoord = -EntireGraphingPanel.LEFT_OFFSCREEN_POINTS_ZOOMED_OUT;
140 		if (!zoomedOut) {
141 			screenPositionXCoord = -(int) Math.round(offset) - (EntireGraphingPanel.LEFT_OFFSCREEN_POINTS_ZOOMED_IN * distanceBetweenDataPoints); // Ugly cast/invert here too
142 		}
143 		int screenPositionYCoord = Integer.MIN_VALUE;
144 		int nextScreenPositionYCoord = getScreenPositionYCoord(rightOfTraceData, gde.getDisplayMinValue(), gde.getDisplayMaxValue());
145 
146 		// Draw data points and trace lines from left to right including one off screen to the right
147 		for (int i = 0; i < dataPointsToDisplay.length; i++) {
148 			// Setup current, previous and next graph trace data points
149 			if (i > 0) {
150 				leftOfTraceData = dataPointsToDisplay[i - 1];
151 			} else {
152 				leftOfTraceData = -Double.MAX_VALUE;
153 			}
154 
155 			traceData = dataPointsToDisplay[i];
156 
157 			if (i + 1 < dataPointsToDisplay.length) {
158 				rightOfTraceData = dataPointsToDisplay[i + 1];
159 			} else {
160 				rightOfTraceData = -Double.MAX_VALUE;
161 			}
162 
163 			// Setup data point screen location stuff
164 			screenPositionYCoord = nextScreenPositionYCoord;
165 			nextScreenPositionYCoord = getScreenPositionYCoord(rightOfTraceData, gde.getDisplayMinValue(), gde.getDisplayMaxValue());
166 
167 			// Draw beginning and end markers
168 			if (screenPositionYCoord >= graphTraceMinHeight && screenPositionYCoord <= graphTraceMaxHeight) {
169 				if (i == graphBeginningIndex || i == graphEndingIndex) {
170 					g2d.drawLine(screenPositionXCoord - 2, screenPositionYCoord - 1, screenPositionXCoord - 2, screenPositionYCoord + 1);
171 					g2d.drawLine(screenPositionXCoord - 1, screenPositionYCoord + 2, screenPositionXCoord + 1, screenPositionYCoord + 2);
172 					g2d.drawLine(screenPositionXCoord - 1, screenPositionYCoord - 2, screenPositionXCoord + 1, screenPositionYCoord - 2);
173 					g2d.drawLine(screenPositionXCoord + 2, screenPositionYCoord - 1, screenPositionXCoord + 2, screenPositionYCoord + 1);
174 				}
175 			}
176 
177 			// Setup graph states
178 			if (leftOfTraceData == -Double.MAX_VALUE && traceData != -Double.MAX_VALUE) {
179 				// At the beginning of the portion of the graph trace designated for display
180 				atTraceBeginning = true;
181 				insideTrace = true;
182 			}
183 
184 			if (traceData != -Double.MAX_VALUE && rightOfTraceData == -Double.MAX_VALUE) {
185 				// At the end of the portion of the graph trace designated for display
186 				atTraceEnd = true;
187 			}
188 
189 			// Draw data point
190 			if (!zoomedOut && distanceBetweenDataPoints > SHOW_DATA_POINT_ZOOM_THRESHOLD && screenPositionYCoord >= graphTraceMinHeight && screenPositionYCoord <= graphTraceMaxHeight) {
191 				// Draw fat data point
192 				if (atTraceBeginning && atTraceEnd) {
193 					// Special case to determine if fat dot is needed if scrolled to the end
194 					if (availableDataRecords >= 2 && gde.get(availableDataRecords - 2) != traceData) {
195 						// fillRect() is 95% faster than fillOval() for a 3x3 square on Ben's dev machine
196 						g2d.fillRect(screenPositionXCoord - 1, screenPositionYCoord - 1, DATA_POINT_WIDTH, DATA_POINT_HEIGHT);
197 					} else {
198 						// Draw small data point
199 						// drawLine() is 33% faster than fillRect() for a single pixel on Ben's dev machine
200 						g2d.drawLine(screenPositionXCoord, screenPositionYCoord, screenPositionXCoord, screenPositionYCoord);
201 					}
202 				} else if (atTraceBeginning) {
203 					if (traceData != rightOfTraceData) {
204 						// fillRect() is 95% faster than fillOval() for a 3x3 square on Ben's dev machine
205 						g2d.fillRect(screenPositionXCoord - 1, screenPositionYCoord - 1, DATA_POINT_WIDTH, DATA_POINT_HEIGHT);
206 					}
207 				} else if (atTraceEnd) {
208 					if (traceData != leftOfTraceData) {
209 						// fillRect() is 95% faster than fillOval() for a 3x3 square on Ben's dev machine
210 						g2d.fillRect(screenPositionXCoord - 1, screenPositionYCoord - 1, DATA_POINT_WIDTH, DATA_POINT_HEIGHT);
211 					}
212 				} else if (insideTrace) {
213 					if (traceData != leftOfTraceData || traceData != rightOfTraceData) {
214 						// fillRect() is 95% faster than fillOval() for a 3x3 square on Ben's dev machine
215 						g2d.fillRect(screenPositionXCoord - 1, screenPositionYCoord - 1, DATA_POINT_WIDTH, DATA_POINT_HEIGHT);
216 					}
217 				}
218 			} else if (insideTrace && screenPositionYCoord >= graphTraceMinHeight && screenPositionYCoord <= graphTraceMaxHeight) {
219 				// Draw small data point
220 				// drawLine() is 33% faster than fillRect() for a single pixel on Ben's dev machine
221 				g2d.drawLine(screenPositionXCoord, screenPositionYCoord, screenPositionXCoord, screenPositionYCoord);
222 			}
223 
224 			// Draw graph trace line
225 			if (insideTrace && !atTraceEnd) {
226 				final boolean currentPointWithinTrack = screenPositionYCoord >= graphTraceMinHeight && screenPositionYCoord <= graphTraceMaxHeight;
227 				final boolean nextPointWithinTrack = nextScreenPositionYCoord >= graphTraceMinHeight && nextScreenPositionYCoord <= graphTraceMaxHeight;
228 				if (currentPointWithinTrack && nextPointWithinTrack) { // Just draw it!
229 					g2d.drawLine(screenPositionXCoord, screenPositionYCoord, screenPositionXCoord + distanceBetweenDataPoints, nextScreenPositionYCoord);
230 				} else if (currentPointWithinTrack) {
231 					if (nextScreenPositionYCoord >= graphTraceMinHeight) { // Next point is below track
232 						final float currentDistAboveBottom = graphTraceMaxHeight - screenPositionYCoord;
233 						final float nextDistBelowBottom = nextScreenPositionYCoord - graphTraceMaxHeight;
234 						final int nextScreenPositionXCoord = screenPositionXCoord + Math.round(distanceBetweenDataPoints * (currentDistAboveBottom / (currentDistAboveBottom + nextDistBelowBottom)));
235 						g2d.drawLine(screenPositionXCoord, screenPositionYCoord, nextScreenPositionXCoord, graphTraceMaxHeight);
236 					} else if (nextScreenPositionYCoord <= graphTraceMaxHeight) { // Next point is above track
237 						final float currentDistBelowTop = screenPositionYCoord - graphTraceMinHeight;
238 						final float nextDistAboveTop = graphTraceMinHeight - nextScreenPositionYCoord;
239 						final int nextScreenPositionXCoord = screenPositionXCoord + Math.round(distanceBetweenDataPoints * (currentDistBelowTop / (currentDistBelowTop + nextDistAboveTop)));
240 						g2d.drawLine(screenPositionXCoord, screenPositionYCoord, nextScreenPositionXCoord, graphTraceMinHeight);
241 					}
242 				} else if (nextPointWithinTrack) {
243 					if (screenPositionYCoord >= graphTraceMinHeight) { // Current point is below track
244 						final float currentDistBelowBottom = screenPositionYCoord - graphTraceMaxHeight;
245 						final float nextDistAboveBottom = graphTraceMaxHeight - nextScreenPositionYCoord;
246 						final int currentScreenPositionXCoord = screenPositionXCoord + Math.round(distanceBetweenDataPoints * (currentDistBelowBottom / (currentDistBelowBottom + nextDistAboveBottom)));
247 						g2d.drawLine(currentScreenPositionXCoord, graphTraceMaxHeight, screenPositionXCoord + distanceBetweenDataPoints, nextScreenPositionYCoord);
248 					} else if (screenPositionYCoord <= graphTraceMaxHeight) { // Current point is above track
249 						final float currentDistAboveTop = graphTraceMinHeight - screenPositionYCoord;
250 						final float nextDistBelowTop = nextScreenPositionYCoord - graphTraceMinHeight;
251 						final int currentScreenPositionXCoord = screenPositionXCoord + Math.round(distanceBetweenDataPoints * (currentDistAboveTop / (currentDistAboveTop + nextDistBelowTop)));
252 						g2d.drawLine(currentScreenPositionXCoord, graphTraceMinHeight, screenPositionXCoord + distanceBetweenDataPoints, nextScreenPositionYCoord);
253 					}
254 				} //  else current and next points are outside of the track so no line is drawn
255 			}
256 
257 			// Reset graph states
258 			if (atTraceEnd) {
259 				insideTrace = false;
260 				atTraceEnd = false;
261 			}
262 			atTraceBeginning = false;
263 
264 			// Move to the right in preparation of drawing more
265 			screenPositionXCoord += distanceBetweenDataPoints;
266 		}
267 	}
268 
269 	/**
270 	 * Cases:
271 	 *
272 	 * data > max = don't display
273 	 * data < min = don't display
274 	 * data == min == max > 0 = top
275 	 * data == min == max <= 0 = bottom
276 	 * otherwise = proportional
277 	 *
278 	 * @param traceData The value of the actual data at this point.
279 	 * @param minValue The minimum value to display before being cut off.
280 	 * @param maxValue The maximum value to display before being cut off.
281 	 * @return
282 	 */
283 	private int getScreenPositionYCoord(final Double traceData, final double minValue, final double maxValue) {
284 		int yCoord = (int) (graphTraceMaxHeight - ((graphTraceMaxHeight - graphTraceMinHeight) * ((traceData - minValue) / (maxValue - minValue))));
285 		if (maxValue == minValue) { // Entire trace must all be equal in value for this to happen
286 			if (traceData > 0D) {
287 				yCoord = graphTraceMinHeight; // Max and min equal, and data is greater than zero so data is at top
288 			} else {
289 				yCoord = graphTraceMaxHeight; // Max and min equal, and data is zero or less so data is at bottom
290 			}
291 		}
292 		return yCoord;
293 	}
294 
295 	private boolean hasDataPointToDisplay() {
296 		boolean result = false;
297 		if ((dataPointsToDisplay != null) && (dataPointsToDisplay.length > 0)) {
298 			result = true;
299 		}
300 		return result;
301 	}
302 
303 	/**
304 	 * this is where the GDE is referenced and the graph gets initialized for the first time
305 	 * @param newGDE
306 	 */
307 	public final void setData(final GenericDataElement newGDE) {
308 		gde = newGDE;
309 		availableDataRecords = newGDE.size() + 1; // Size is currently position, this will need cleaning up later, leave it to me.
310 		// The main thing is to take away 10 calls to the GDE per view on something that is fairly static and cache it internally
311 		sizeGraph();
312 	}
313 
314 	public final GenericDataElement getData() {
315 		return gde;
316 	}
317 
318 	/**
319 	 * Used for InfoLayer to get the data from the single graphs for data under the mouse
320 	 *
321 	 * @param cursorPosition
322 	 * @param requiredWidth
323 	 * @return Double representation of info at the mouse cursor line which snaps to data points or null if no data under cursor
324 	 */
325 	public final String getMouseInfo(final int cursorPosition, final int requiredWidth) {
326 		final boolean zoomedOut = OpenLogViewer.getInstance().getEntireGraphingPanel().isZoomedOutBeyondOneToOne();
327 		String info;
328 		if (zoomedOut) {
329 			info = getMouseInfoZoomedOut(cursorPosition, requiredWidth);
330 		} else {
331 			info = getMouseInfoZoomed(cursorPosition, requiredWidth);
332 		}
333 
334 		return info;
335 	}
336 
337 	/**
338 	 * Used for InfoLayer to get the data from the single graphs for data under the mouse when not zoomed out
339 	 *
340 	 * @param pointerDistanceFromCenter
341 	 * @return Double representation of info at the mouse cursor line which snaps to data points or null if no data under cursor
342 	 */
343 	private String getMouseInfoZoomed(final int cursorPosition, final int requiredWidth) {
344 		final double graphPosition = OpenLogViewer.getInstance().getEntireGraphingPanel().getGraphPosition();
345 		final int zoom = OpenLogViewer.getInstance().getEntireGraphingPanel().getZoom();
346 		final double offset = (graphPosition % 1) * zoom;
347 		final int cursorPositionPlusOffset = cursorPosition + (int) offset;
348 		double numSnapsFromLeft = ((double) cursorPositionPlusOffset / (double) zoom);
349 		numSnapsFromLeft = Math.round(numSnapsFromLeft);
350 		final int dataLocation = (int) graphPosition + (int) numSnapsFromLeft;
351 		String result;
352 		if ((dataLocation >= 0) && (dataLocation < availableDataRecords)) {
353 			result = MathUtils.roundDecimalPlaces(gde.get(dataLocation), DECIMAL_PLACES);
354 			result = padWithLeadingSpaces(result, requiredWidth);
355 			result = replaceDecimalZerosWithSpaces(result);
356 		} else {
357 			result = UNKNOWN_NUMBER;
358 		}
359 		return result;
360 	}
361 
362 	/**
363 	 * Used for InfoLayer to get the data from the single graphs for data under the mouse when zoomed out
364 	 *
365 	 * @param pointerDistanceFromCenter
366 	 * @return Double representation of info at the mouse cursor line which snaps to data points or null if no data under cursor
367 	 */
368 	private String getMouseInfoZoomedOut(final int cursorPosition, final int requiredWidth) {
369 		String result = UNKNOWN_STATS;
370 		final int dataPointRangeInfoIndex = cursorPosition + EntireGraphingPanel.LEFT_OFFSCREEN_POINTS_ZOOMED_OUT;
371 		if ((dataPointRangeInfoIndex >= 0) && (dataPointRangeInfoIndex < dataPointRangeInfo.length)) {
372 			final double minData = dataPointRangeInfo[dataPointRangeInfoIndex][0];
373 			final double meanData = dataPointRangeInfo[dataPointRangeInfoIndex][1];
374 			final double maxData = dataPointRangeInfo[dataPointRangeInfoIndex][2];
375 			if (minData != -Double.MAX_VALUE) {
376 				String resultMin = MathUtils.roundDecimalPlaces(minData, DECIMAL_PLACES);
377 				String resultMax = MathUtils.roundDecimalPlaces(maxData, DECIMAL_PLACES);
378 				String resultMean = MathUtils.roundDecimalPlaces(meanData, DECIMAL_PLACES);
379 
380 				resultMin = padWithLeadingSpaces(resultMin, requiredWidth);
381 				resultMax = padWithLeadingSpaces(resultMax, requiredWidth);
382 				resultMean = padWithLeadingSpaces(resultMean, requiredWidth);
383 
384 				resultMin = replaceDecimalZerosWithSpaces(resultMin);
385 				resultMax = replaceDecimalZerosWithSpaces(resultMax);
386 				resultMean = replaceDecimalZerosWithSpaces(resultMean);
387 
388 				result = resultMin + STAT_SEPARATOR + resultMean + STAT_SEPARATOR + resultMax;
389 			}
390 		}
391 		return result;
392 	}
393 
394 	private String padWithLeadingSpaces(final String input, final int requiredWidth) {
395 		final StringBuilder padded = new StringBuilder(input);
396 		while (padded.length() < requiredWidth) {
397 			padded.insert(0, ' ');
398 		}
399 		return padded.toString();
400 	}
401 
402 	private String replaceDecimalZerosWithSpaces(final String input) {
403 		if (input != null && !input.isEmpty()) {
404 			final StringBuilder stripped = new StringBuilder(input);
405 			for (int i = stripped.length() - 1; stripped.charAt(i - 1) != DS; i--) {
406 				if (stripped.charAt(i) == '0') {
407 					stripped.setCharAt(i, ' ');
408 				}
409 			}
410 			return stripped.toString();
411 		} else {
412 			return input;
413 		}
414 	}
415 
416 	public final Color getColor() {
417 		return gde.getDisplayColor();
418 	}
419 
420 	public final void setColor(final Color c) {
421 		gde.setDisplayColor(c);
422 	}
423 
424 	public final void initGraph() {
425 		if (OpenLogViewer.getInstance().getEntireGraphingPanel().isZoomedOutBeyondOneToOne()) {
426 			initGraphZoomedOut();
427 		} else {
428 			initGraphZoomed();
429 		}
430 	}
431 
432 	/**
433 	 * initialize the graph any time you need to paint
434 	 */
435 	public final void initGraphZoomed() {
436 		if (gde != null) {
437 			final int graphPosition = (int) OpenLogViewer.getInstance().getEntireGraphingPanel().getGraphPosition();
438 			final int graphWindowWidth = OpenLogViewer.getInstance().getEntireGraphingPanel().getWidth();
439 			final int zoom = OpenLogViewer.getInstance().getEntireGraphingPanel().getZoom();
440 			int numberOfPointsThatFitInDisplay = graphWindowWidth / zoom;
441 			numberOfPointsThatFitInDisplay += EntireGraphingPanel.LEFT_OFFSCREEN_POINTS_ZOOMED_IN;
442 			numberOfPointsThatFitInDisplay += EntireGraphingPanel.RIGHT_OFFSCREEN_POINTS_ZOOMED_IN;
443 			dataPointsToDisplay = new double[numberOfPointsThatFitInDisplay];
444 			int position = graphPosition - EntireGraphingPanel.LEFT_OFFSCREEN_POINTS_ZOOMED_IN;
445 
446 			// Reset start/end indices.
447 			graphBeginningIndex = Integer.MIN_VALUE;
448 			graphEndingIndex = Integer.MIN_VALUE;
449 
450 			// Setup data points.
451 			for (int i = 0; i < numberOfPointsThatFitInDisplay; i++) {
452 				if (position >= 0 && position < availableDataRecords) {
453 					dataPointsToDisplay[i] = gde.get(position);
454 
455 					// Set start/end indices.
456 					if (position == 0) {
457 						graphBeginningIndex = i;
458 					}
459 
460 					if (position == availableDataRecords - 1) {
461 						graphEndingIndex = i;
462 					}
463 
464 				} else {
465 					dataPointsToDisplay[i] = -Double.MAX_VALUE;
466 				}
467 				position++;
468 			}
469 		}
470 	}
471 
472 	/**
473 	 * initialize the graph any time you need to paint
474 	 */
475 	public final void initGraphZoomedOut() {
476 		if (gde != null) {
477 			final EntireGraphingPanel egp = OpenLogViewer.getInstance().getEntireGraphingPanel();
478 			final int graphPosition = (int) egp.getGraphPosition();
479 			final int graphWindowWidth = egp.getWidth();
480 			final int zoom = egp.getZoom();
481 			final int position = graphPosition - (EntireGraphingPanel.LEFT_OFFSCREEN_POINTS_ZOOMED_OUT * zoom);
482 			dataPointsToDisplay = new double[graphWindowWidth
483 			                                 + EntireGraphingPanel.LEFT_OFFSCREEN_POINTS_ZOOMED_OUT
484 			                                 + EntireGraphingPanel.RIGHT_OFFSCREEN_POINTS_ZOOMED_OUT];
485 			dataPointRangeInfo = new double[dataPointsToDisplay.length][3];
486 			final int numberOfRealPointsThatFitInDisplay = (graphWindowWidth * zoom)
487 					+ (EntireGraphingPanel.LEFT_OFFSCREEN_POINTS_ZOOMED_OUT * zoom)
488 					+ (EntireGraphingPanel.RIGHT_OFFSCREEN_POINTS_ZOOMED_OUT * zoom);
489 			final int rightGraphPosition = position + numberOfRealPointsThatFitInDisplay;
490 
491 			// Reset start/end indices.
492 			graphBeginningIndex = Integer.MIN_VALUE;
493 			graphEndingIndex = Integer.MIN_VALUE;
494 
495 			/*
496 			* Setup data points.
497 			*
498 			* The data point to display is calculated by taking the average of
499 			* the data point spread and comparing it to the previous calculated
500 			* data point. If the average is higher, then the highest value of
501 			* the data spread is used. If the average is lower, then the lowest
502 			* value of the data point spread is used.
503 			*
504 			* In other words, if the graph is trending upward, the peak is used.
505 			* If the graph is trending downward, the valley is used.
506 			* This keeps the peaks and valleys intact and the middle stuff is
507 			* lost. This maintains the general shape of the graph, and assumes
508 			* that local peaks and valleys are the most interesting parts of the
509 			* graph to display.
510 			*/
511 			int nextAarrayIndex = 0;
512 			double leftOfNewData = gde.get(0);
513 			if (position > 0 && position < availableDataRecords) {
514 				leftOfNewData = gde.get(position);
515 			}
516 
517 			if (zoom < (availableDataRecords / 2)) {
518 
519 				for (int i = position; i < rightGraphPosition; i += zoom) {
520 
521 					if (i >= 0 && i < availableDataRecords) {
522 						double minData = Double.MAX_VALUE;
523 						double maxData = -Double.MAX_VALUE;
524 						double newData = 0.0;
525 						double acummulateData = 0.0;
526 						int divisor = 0;
527 
528 						for (int j = 0; j < zoom; j++) {
529 							final int gdeIndex = i + j;
530 							if (gdeIndex >= 0 && gdeIndex < availableDataRecords) {
531 								newData = gde.get(gdeIndex);
532 								acummulateData += newData;
533 								divisor++;
534 								if (newData < minData) {
535 									minData = newData;
536 								}
537 
538 								if (newData > maxData) {
539 									maxData = newData;
540 								}
541 
542 								// Set start/end indices.
543 								if (graphBeginningIndex == Integer.MIN_VALUE && (gdeIndex >= 0 && gdeIndex < zoom)) {
544 									graphBeginningIndex = nextAarrayIndex;
545 								}
546 
547 								if (gdeIndex == availableDataRecords - 1) {
548 									graphEndingIndex = nextAarrayIndex;
549 								}
550 
551 							}
552 						}
553 						final double averageData = acummulateData / divisor;
554 						if (averageData > leftOfNewData) {
555 							dataPointsToDisplay[nextAarrayIndex] = maxData;
556 							leftOfNewData = maxData;
557 						} else if (averageData < leftOfNewData) {
558 							dataPointsToDisplay[nextAarrayIndex] = minData;
559 							leftOfNewData = minData;
560 						} else {
561 							dataPointsToDisplay[nextAarrayIndex] = averageData;
562 							leftOfNewData = averageData;
563 						}
564 						dataPointRangeInfo[nextAarrayIndex][0] = minData;
565 						dataPointRangeInfo[nextAarrayIndex][1] = averageData;
566 						dataPointRangeInfo[nextAarrayIndex][2] = maxData;
567 						nextAarrayIndex++;
568 					} else {
569 						dataPointsToDisplay[nextAarrayIndex] = -Double.MAX_VALUE;
570 						dataPointRangeInfo[nextAarrayIndex][0] = -Double.MAX_VALUE;
571 						dataPointRangeInfo[nextAarrayIndex][1] = -Double.MAX_VALUE;
572 						dataPointRangeInfo[nextAarrayIndex][2] = -Double.MAX_VALUE;
573 						nextAarrayIndex++;
574 					}
575 				}
576 			} else {
577 
578 				/*
579 				* Setup data points when extremely zoomed out.
580 				*
581 				* If the zoom value is higher than the entire length of
582 				* available data then it is possible for the normal algorithm
583 				* to skip over the data completely when sweeping across the
584 				* screen from left to right in zoom steps. If zoom reaches
585 				* that high of a value, then use this alternative algorithm
586 				* instead.
587 				*
588 				*/
589 
590 				// Fill in null data points until zero position is reached.
591 				for (int i = position; i < 0; i += zoom) {
592 					dataPointsToDisplay[nextAarrayIndex] = -Double.MAX_VALUE;
593 					dataPointRangeInfo[nextAarrayIndex][0] = -Double.MAX_VALUE;
594 					dataPointRangeInfo[nextAarrayIndex][1] = -Double.MAX_VALUE;
595 					dataPointRangeInfo[nextAarrayIndex][2] = -Double.MAX_VALUE;
596 					nextAarrayIndex++;
597 				}
598 
599 				// Find min/mean/max of entire available data and place at position zero.
600 				double minData = Double.MAX_VALUE;
601 				double maxData = -Double.MAX_VALUE;
602 				double newData = 0.0;
603 				double acummulateData = 0.0;
604 				int divisor = 0;
605 				for (int i = 0; i < availableDataRecords; i++) {
606 					newData = gde.get(i);
607 					acummulateData += newData;
608 					divisor++;
609 					if (newData < minData) {
610 						minData = newData;
611 					}
612 
613 					if (newData > maxData) {
614 						maxData = newData;
615 					}
616 				}
617 				final double averageData = acummulateData / divisor;
618 				dataPointsToDisplay[nextAarrayIndex] = averageData;
619 				dataPointRangeInfo[nextAarrayIndex][0] = minData;
620 				dataPointRangeInfo[nextAarrayIndex][1] = averageData;
621 				dataPointRangeInfo[nextAarrayIndex][2] = maxData;
622 
623 				// Set start/end indices.
624 				graphBeginningIndex = nextAarrayIndex;
625 				graphEndingIndex = nextAarrayIndex;
626 
627 				nextAarrayIndex++;
628 
629 				// Fill in the rest of the array with null data points.
630 				for (int i = zoom * 2; i < rightGraphPosition; i += zoom) {
631 					dataPointsToDisplay[nextAarrayIndex] = -Double.MAX_VALUE;
632 					dataPointRangeInfo[nextAarrayIndex][0] = -Double.MAX_VALUE;
633 					dataPointRangeInfo[nextAarrayIndex][1] = -Double.MAX_VALUE;
634 					dataPointRangeInfo[nextAarrayIndex][2] = -Double.MAX_VALUE;
635 					nextAarrayIndex++;
636 				}
637 			}
638 		}
639 	}
640 
641 	/**
642 	 * maintains the size and position of the graph when adding or removing tracks
643 	 */
644 	public final void sizeGraph() {
645 		final MultiGraphLayeredPane multiGraph = OpenLogViewer.getInstance().getMultiGraphLayeredPane();
646 		final int trackCount = multiGraph.getTrackCount();
647 		final float height = ((float) multiGraph.getHeight() / trackCount);
648 		final int yCoord = (int) (gde.getTrackIndex() * height);
649 		setBounds(0, yCoord, multiGraph.getWidth(), (int) height);
650 		final float totalMargin = 1F - GRAPH_TRACE_HEIGHT_AS_PERCENTAGE_OF_TOTAL_TRACK_HEIGHT;
651 		final float halfMargin = totalMargin / 2F;
652 
653 		graphTraceMinHeight = Math.round(height * halfMargin);
654 		graphTraceMaxHeight = Math.round(height - (height * halfMargin));
655 
656 		// Make sure there is always enough space for the start/end points to display
657 		if (graphTraceMinHeight < MINIMUM_MARGIN_PIXELS) {
658 			graphTraceMinHeight = MINIMUM_MARGIN_PIXELS;
659 		} else if (graphTraceMaxHeight > ((Math.ceil(height) - MINIMUM_MARGIN_PIXELS) - 1)) {
660 			// The subtraction of one is because the index of the pixel includes the pixel at the bottom
661 			graphTraceMaxHeight = (int) ((Math.ceil(height) - MINIMUM_MARGIN_PIXELS) - 1);
662 		}
663 
664 		initGraph();
665 	}
666 
667 	/**
668 	 * Graph total size
669 	 * @return GDE.size()
670 	 */
671 	public final int graphSize() {
672 		return availableDataRecords;
673 	}
674 }