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.FontMetrics;
27  import java.awt.Graphics;
28  import java.awt.Graphics2D;
29  import java.math.BigDecimal;
30  
31  import javax.swing.JPanel;
32  
33  import org.diyefi.openlogviewer.FramesPerSecondPanel;
34  import org.diyefi.openlogviewer.OpenLogViewer;
35  import org.diyefi.openlogviewer.genericlog.GenericLog;
36  import org.diyefi.openlogviewer.utils.MathUtils;
37  
38  public class GraphPositionPanel extends JPanel {
39  	private static final long serialVersionUID = 1L;
40  
41  	private static final int TEXT_Y_OFFSET = 18;
42  	private static final double TENTHS_DISPLAY_THRESHOLD = 0.5;
43  	private static final double HUNDRETHS_DISPLAY_THRESHOLD = 0.05;
44  	private static final double INITIAL_MAJOR_GRADUATION_SPACING = 100.0;
45  
46  	private GenericLog genLog;
47  	private final Color majorGraduationColor;
48  	private final Color positionDataColor;
49  	private final Color backgroundColor;
50  	private boolean[] validSnappingPositions;
51  	private final double[] graduationSpacingMultiplier;
52  	private double majorGraduationSpacing;
53  
54  	public GraphPositionPanel() {
55  		setOpaque(true);
56  		setLayout(null);
57  		backgroundColor = Color.BLACK;
58  
59  		majorGraduationColor = Color.GRAY;
60  		positionDataColor = majorGraduationColor;
61  		validSnappingPositions = new boolean[this.getWidth()];
62  		graduationSpacingMultiplier = new double[] {2.0, 2.5, 2.0};
63  		setGraduationSpacing();
64  	}
65  
66  	@Override
67  	public final void paintComponent(final Graphics g) {
68  		super.paintComponent(g);
69  
70  		if (!this.getSize().equals(this.getParent().getSize())) {
71  			this.setSize(this.getParent().getSize());
72  		}
73  
74  		setGraduationSpacing();
75  		final Graphics2D g2d = (Graphics2D) g;
76  		g2d.setColor(backgroundColor);
77  		g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
78  		if (genLog ==  null) {
79  			paintPositionBar(g2d, false);
80  		} else {
81  			if (genLog.getLogStatus() == GenericLog.LogState.LOG_LOADING) {
82  				paintPositionBar(g2d, false);
83  			} else if (genLog.getLogStatus() == GenericLog.LogState.LOG_LOADED) {
84  				final int zoom = OpenLogViewer.getInstance().getEntireGraphingPanel().getZoom();
85  				final boolean zoomedOut = OpenLogViewer.getInstance().getEntireGraphingPanel().isZoomedOutBeyondOneToOne();
86  				if (!zoomedOut || zoom == 1) {
87  					setupMouseCursorLineSnappingPositions();
88  				}
89  				paintPositionBar(g2d, zoomedOut);
90  				paintPositionData(g2d, zoomedOut);
91  			}
92  		}
93  		FramesPerSecondPanel.increaseFrameCount();
94  	}
95  
96  	private void paintPositionBar(final Graphics2D g2d, final boolean zoomedOut) {
97  		final double graphPosition = OpenLogViewer.getInstance().getEntireGraphingPanel().getGraphPosition();
98  		final int zoom = OpenLogViewer.getInstance().getEntireGraphingPanel().getZoom();
99  		double offset = 0d;
100 		double margin = 0d;
101 		if (zoomedOut) {
102 			offset = majorGraduationSpacing / zoom;
103 			offset = Math.ceil(offset);
104 			margin = (1d / zoom) / 2d;
105 		} else {
106 			offset = majorGraduationSpacing * zoom;
107 			offset = Math.round(offset);
108 			margin = (1d / zoom) / 2d;
109 		}
110 
111 		g2d.setColor(majorGraduationColor);
112 
113 		// Find first position marker placement
114 		double nextPositionMarker = getFirstPositionMarkerPlacement();
115 
116 		// Paint left to right
117 		double position = graphPosition - majorGraduationSpacing;
118 
119 		// TODO It's ugly having the - on the left side of the cast,
120 		// but moving it *could* change behavior, so leaving it alone and
121 		// adding this instead!
122 		for (int i = -(int) offset; i < this.getWidth() + (int) offset; i++) {
123 			if (position >= nextPositionMarker - margin) {
124 				int xCoord = i;
125 				if (xCoord >= 0 && xCoord < validSnappingPositions.length && !validSnappingPositions[xCoord]) {
126 					if (xCoord + 1 < validSnappingPositions.length && validSnappingPositions[xCoord + 1]) {
127 						xCoord++;
128 					} else if (xCoord > 0 && validSnappingPositions[xCoord - 1]) {
129 						xCoord--;
130 					}
131 				}
132 				g2d.drawLine(xCoord, 0, xCoord, 6);
133 				nextPositionMarker += majorGraduationSpacing;
134 			}
135 			if (zoomedOut) {
136 				position += zoom;
137 			} else {
138 				position += (1d / zoom);
139 			}
140 		}
141 		g2d.drawLine(0, 0, this.getWidth(), 0);
142 	}
143 
144 	private void paintPositionData(final Graphics2D g2d, final boolean zoomedOut) {
145 		final double graphPosition = OpenLogViewer.getInstance().getEntireGraphingPanel().getGraphPosition();
146 		final int zoom = OpenLogViewer.getInstance().getEntireGraphingPanel().getZoom();
147 		double offset = 0d;
148 		double margin = 0d;
149 		if (zoomedOut) {
150 			offset = majorGraduationSpacing / zoom;
151 			offset = Math.ceil(offset);
152 			margin = (1d / zoom) / 2d;
153 		} else {
154 			offset = majorGraduationSpacing * zoom;
155 			offset = Math.round(offset);
156 			margin = (1d / zoom) / 2d;
157 		}
158 		g2d.setColor(positionDataColor);
159 		final FontMetrics fm = this.getFontMetrics(this.getFont()); // For getting string width
160 
161 		// Find first position marker placement
162 		double nextPositionMarker = getFirstPositionMarkerPlacement();
163 
164 		// Paint left to right
165 		double position = graphPosition - majorGraduationSpacing;
166 		for (int i = -(int) offset; i < this.getWidth() + (int) offset; i++) { // TODO Ditto!
167 			if (position >= nextPositionMarker - margin) {
168 				int xCoord = i;
169 				if (xCoord >= 0 && xCoord < validSnappingPositions.length) {
170 					// Check this first to see if there is no need to modify xCoord.
171 					if (!validSnappingPositions[xCoord]) {
172 						if (xCoord + 1 < validSnappingPositions.length && validSnappingPositions[xCoord + 1]) {
173 							xCoord++;
174 						} else if (xCoord > 0 && validSnappingPositions[xCoord - 1]) {
175 							xCoord--;
176 						}
177 					}
178 				}
179 
180 				String positionDataString;
181 				if (majorGraduationSpacing > TENTHS_DISPLAY_THRESHOLD) {
182 					final BigDecimal positionData = new BigDecimal(nextPositionMarker);
183 					positionDataString = positionData.toPlainString();
184 				} else if (majorGraduationSpacing > HUNDRETHS_DISPLAY_THRESHOLD) {
185 					positionDataString = MathUtils.roundDecimalPlaces(nextPositionMarker, 1);
186 				} else {
187 					positionDataString = MathUtils.roundDecimalPlaces(nextPositionMarker, 2);
188 				}
189 				final int stringWidth = fm.stringWidth(positionDataString);
190 				g2d.drawString(positionDataString, xCoord - (stringWidth / 2), TEXT_Y_OFFSET);
191 
192 				nextPositionMarker += majorGraduationSpacing;
193 			}
194 			if (zoomedOut) {
195 				position += zoom;
196 			} else {
197 				position += (1.0 / zoom);
198 			}
199 		}
200 	}
201 
202 	private double getFirstPositionMarkerPlacement() {
203 		final double graphPosition = OpenLogViewer.getInstance().getEntireGraphingPanel().getGraphPosition();
204 
205 		double nextPositionMarker = 0d;
206 		if (graphPosition < 0d) {
207 			while (nextPositionMarker - graphPosition >= majorGraduationSpacing) {
208 				nextPositionMarker -= majorGraduationSpacing;
209 			}
210 		} else {
211 			while (nextPositionMarker - graphPosition < 0.0) {
212 				nextPositionMarker += majorGraduationSpacing;
213 			}
214 		}
215 
216 		nextPositionMarker -= majorGraduationSpacing; // Start with one graduation off-screen to the left
217 		return nextPositionMarker;
218 	}
219 
220 	private void setupMouseCursorLineSnappingPositions() {
221 		validSnappingPositions = new boolean[this.getWidth()];
222 		final double graphPosition = OpenLogViewer.getInstance().getEntireGraphingPanel().getGraphPosition();
223 		final int zoom = OpenLogViewer.getInstance().getEntireGraphingPanel().getZoom();
224 		final MultiGraphLayeredPane multiGraph = OpenLogViewer.getInstance().getEntireGraphingPanel().getMultiGraphLayeredPane();
225 		final int availableData = (multiGraph.graphSize() - 1) * zoom;
226 		long count = Math.round(graphPosition * zoom);
227 
228 		// Fill array with valid snapping points from left to right
229 		for (int i = 0; i < this.getWidth(); i++) {
230 			if (count < -1 || count > availableData + 1 || count % zoom == 0) {
231 				validSnappingPositions[i] = true;
232 			}
233 			count++;
234 		}
235 	}
236 
237 	public final void setLog(final GenericLog log) {
238 		genLog = log;
239 		repaint();
240 	}
241 
242 	private void setGraduationSpacing() {
243 		int zoom = 1;
244 		boolean zoomedOut = false;
245 		if (OpenLogViewer.getInstance() != null) {
246 			zoom = OpenLogViewer.getInstance().getEntireGraphingPanel().getZoom();
247 			zoomedOut = OpenLogViewer.getInstance().getEntireGraphingPanel().isZoomedOutBeyondOneToOne();
248 		}
249 
250 		majorGraduationSpacing = INITIAL_MAJOR_GRADUATION_SPACING;
251 		final int count = (int) (Math.log((double) zoom) / Math.log(2.0));  // Base-2 logarithm of zoom
252 
253 		if (zoomedOut) {
254 			for (int i = 0; i < count; i++) {
255 				majorGraduationSpacing *= graduationSpacingMultiplier[i % 3];
256 			}
257 		} else {
258 			for (int i = 0; i < count; i++) {
259 				majorGraduationSpacing /= graduationSpacingMultiplier[i % 3];
260 			}
261 		}
262 	}
263 
264 	public final int getBestSnappingPosition(final int xMouseCoord) {
265 		int bestPosition = xMouseCoord;
266 		if (xMouseCoord < validSnappingPositions.length && !validSnappingPositions[xMouseCoord]) {
267 			boolean found = false;
268 			final int startPosition = xMouseCoord;
269 			for (int distance = 1; !found; distance++) {
270 				final int next = startPosition + distance;
271 				final int prev = startPosition - distance;
272 				if (next > validSnappingPositions.length - 1 || prev < 0) {
273 					bestPosition = xMouseCoord;
274 					found = true;
275 				} else if (validSnappingPositions[next]) {
276 					bestPosition = next;
277 					found = true;
278 				} else if (validSnappingPositions[prev]) {
279 					bestPosition = prev;
280 					found = true;
281 				}
282 			}
283 		}
284 		return bestPosition;
285 	}
286 }