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  
26  import java.awt.Color;
27  import java.awt.Graphics;
28  import java.awt.Graphics2D;
29  import java.awt.event.HierarchyBoundsListener;
30  import java.awt.event.HierarchyEvent;
31  import java.beans.PropertyChangeEvent;
32  import java.beans.PropertyChangeListener;
33  import java.util.LinkedList;
34  import java.util.ListIterator;
35  
36  import javax.swing.JPanel;
37  import org.diyefi.openlogviewer.OpenLogViewerApp;
38  import org.diyefi.openlogviewer.genericlog.GenericDataElement;
39  
40  /**
41   *  GraphLayer is a JPanel that uses a transparent background.
42   * the graph is drawn to this panel and used in conjunction with a JLayeredPane
43   * to give the appearance of the graphs drawn together.
44   *
45   * this Layer listens for window resizes and property changes
46   * @author Bryan Harris
47   */
48  public class SingleGraphPanel extends JPanel implements HierarchyBoundsListener,PropertyChangeListener {
49  
50  
51  	private GenericDataElement GDE;
52      private LinkedList<Double> leftDataPointsToDisplay;
53      private LinkedList<Double> rightDataPointsToDisplay;
54      private static final double GRAPH_TRACE_SIZE_AS_PERCENTAGE_OF_TOTAL_GRAPH_SIZE = 0.95;
55      private static final long serialVersionUID = -7808406950399781712L;
56  
57      public SingleGraphPanel() {
58          this.setOpaque(false);
59          this.setLayout(null);
60          this.GDE = null;
61          leftDataPointsToDisplay = new LinkedList<Double>();
62          rightDataPointsToDisplay = new LinkedList<Double>();
63      }
64  
65      @Override
66      public void ancestorMoved(HierarchyEvent e) {
67      }
68  
69      @Override
70      public void ancestorResized(HierarchyEvent e) {
71          if (e.getID() == HierarchyEvent.ANCESTOR_RESIZED) {
72              sizeGraph();
73          }
74      }
75  
76      @Override
77      public void propertyChange(PropertyChangeEvent evt) {
78          if(evt.getPropertyName().equalsIgnoreCase("Split")){
79              sizeGraph();
80          }
81      }
82  
83  
84  
85      @Override
86      public void paint(Graphics g) { // overridden paint because there will be no other painting other than this
87      	initGraph();
88          if (hasDataPointToDisplay()) {
89          	paintLeftDataPoints(g);
90          	paintRightDataPoints(g);
91          }
92      }
93  
94      private void paintLeftDataPoints(Graphics g){
95      	// Setup graphics stuff
96      	Graphics2D g2d = (Graphics2D) g;
97          g2d.setColor(GDE.getColor());
98  
99          // Setup current, previous and next graph trace data points
100     	boolean atGraphBeginning = false;
101         boolean firstDataPoint = true;
102     	ListIterator<Double> it = (ListIterator<Double>) leftDataPointsToDisplay.iterator();
103     	Double traceData = it.next();
104     	Double leftOfTraceData = null;
105     	Double rightOfTraceData = null;
106     	if(it.hasPrevious()){
107     		rightOfTraceData = it.previous();
108     		it.next();
109     	} else {
110     		if(rightDataPointsToDisplay.size() > 1){
111     			rightOfTraceData = rightDataPointsToDisplay.get(1);
112     		} else {
113     			// At graph end
114     		}
115     	}
116     	if(it.hasNext()){
117     		leftOfTraceData = it.next();
118     		it.previous();
119     	} else {
120     		atGraphBeginning = true;
121     	}
122 
123     	// Setup data point screen location stuff
124     	int zoom = OpenLogViewerApp.getInstance().getEntireGraphingPanel().getZoom();
125     	double graphPosition = OpenLogViewerApp.getInstance().getEntireGraphingPanel().getGraphPosition();
126     	double offset = (graphPosition % 1) * zoom;
127     	int screenPositionXCoord = (this.getWidth() / 2) - (int)offset;
128     	int screenPositionYCoord = getScreenPositionYCoord(traceData, GDE.getMinValue(), GDE.getMaxValue());
129     	int prevScreenPositionYCoord = -1;
130 
131     	// Draw data points and trace lines
132     	while(it.hasNext()){
133 
134     		// Draw data point
135             if(zoom > 5 && (!traceData.equals(leftOfTraceData) || !traceData.equals(rightOfTraceData))){
136                 g2d.fillOval(screenPositionXCoord - 2, screenPositionYCoord - 2, 4, 4);
137             }
138 
139         	// Draw graph trace line
140             if(!firstDataPoint){
141             	g2d.drawLine(screenPositionXCoord, screenPositionYCoord, screenPositionXCoord + zoom, prevScreenPositionYCoord);
142             }
143 
144             // Move to next trace data in the list
145             rightOfTraceData = traceData;
146             traceData = it.next();
147         	if(it.hasNext()){
148         		leftOfTraceData = it.next();
149         		it.previous();
150         	} else {
151         		atGraphBeginning = true;
152         	}
153 
154         	// Reconfigure data point screen location stuff
155             prevScreenPositionYCoord = screenPositionYCoord;
156             screenPositionYCoord = getScreenPositionYCoord(traceData, GDE.getMinValue(), GDE.getMaxValue());
157             screenPositionXCoord -= zoom;
158             firstDataPoint = false;
159     	}
160 
161     	// Always draw one last data point and trace line at the end of the list. This is usually off screen but can also be the beginning of the graph.
162     	if(atGraphBeginning){
163     		screenPositionYCoord = getScreenPositionYCoord(traceData, GDE.getMinValue(), GDE.getMaxValue());
164     		// Draw data point
165     		if (zoom > 5) {
166             	g2d.fillOval(screenPositionXCoord - 2, screenPositionYCoord - 2, 4, 4);
167             }
168         	// Draw graph trace line
169             if(!firstDataPoint){
170             	g2d.drawLine(screenPositionXCoord, screenPositionYCoord, screenPositionXCoord + zoom, prevScreenPositionYCoord);
171             }
172     	}
173     }
174 
175     private void paintRightDataPoints(Graphics g){
176     	// Setup graphics stuff
177     	Graphics2D g2d = (Graphics2D) g;
178         g2d.setColor(GDE.getColor());
179 
180         // Setup current, previous and next graph trace data points
181     	boolean atGraphEnd = false;
182         boolean firstDataPoint = true;
183     	ListIterator<Double> it = (ListIterator<Double>) rightDataPointsToDisplay.iterator();
184     	Double traceData = it.next();
185     	Double leftOfTraceData = null;
186     	Double rightOfTraceData = null;
187     	if(it.hasPrevious()){
188     		leftOfTraceData = it.previous();
189     		it.next();
190     	} else {
191     		if(leftDataPointsToDisplay.size() > 1){
192     			leftOfTraceData = leftDataPointsToDisplay.get(1);
193     		} else {
194     			// At graph beginning
195     		}
196     	}
197     	if(it.hasNext()){
198     		rightOfTraceData = it.next();
199     		it.previous();
200     	} else {
201     		atGraphEnd = true;
202     	}
203 
204     	// Setup data point screen location stuff
205     	int zoom = OpenLogViewerApp.getInstance().getEntireGraphingPanel().getZoom();
206     	double graphPosition = OpenLogViewerApp.getInstance().getEntireGraphingPanel().getGraphPosition();
207     	double offset = (graphPosition % 1) * zoom;
208     	int screenPositionXCoord = (this.getWidth() / 2) - (int)offset;
209     	int screenPositionYCoord = getScreenPositionYCoord(traceData, GDE.getMinValue(), GDE.getMaxValue());
210     	int prevScreenPositionYCoord = -1;
211 
212     	// Draw data points and trace lines
213     	while(it.hasNext()){
214 
215     		// Draw data point
216             if(zoom > 5 && (!traceData.equals(leftOfTraceData) || !traceData.equals(rightOfTraceData))){
217                 g2d.fillOval(screenPositionXCoord - 2, screenPositionYCoord - 2, 4, 4);
218             }
219 
220         	// Draw graph trace line
221             if(!firstDataPoint){
222             	g2d.drawLine(screenPositionXCoord, screenPositionYCoord, screenPositionXCoord - zoom, prevScreenPositionYCoord);
223             }
224 
225             // Move to next trace data in the list
226             leftOfTraceData = traceData;
227             traceData = it.next();
228         	if(it.hasNext()){
229         		rightOfTraceData = it.next();
230         		it.previous();
231         	} else {
232         		atGraphEnd = true;
233         	}
234 
235         	// Reconfigure data point screen location stuff
236             prevScreenPositionYCoord = screenPositionYCoord;
237             screenPositionYCoord = getScreenPositionYCoord(traceData, GDE.getMinValue(), GDE.getMaxValue());
238             screenPositionXCoord += zoom;
239             firstDataPoint = false;
240     	}
241 
242     	// Draw one last data point (if needed) and trace line (always) at the end of the list. This is usually off screen but can also be the end of the graph.
243     	if(atGraphEnd){
244     		screenPositionYCoord = getScreenPositionYCoord(traceData, GDE.getMinValue(), GDE.getMaxValue());
245     		// Draw data point
246     		if (zoom > 5 && !traceData.equals(leftOfTraceData)) {
247             	g2d.fillOval(screenPositionXCoord - 2, screenPositionYCoord - 2, 4, 4);
248             }
249         	// Draw graph trace line
250             if(!firstDataPoint){
251             	g2d.drawLine(screenPositionXCoord, screenPositionYCoord, screenPositionXCoord - zoom, prevScreenPositionYCoord);
252             }
253     	}
254     }
255 
256     private int getScreenPositionYCoord(Double traceData, double minValue, double maxValue) {
257         int point = 0;
258         int height = (int)(this.getHeight() * GRAPH_TRACE_SIZE_AS_PERCENTAGE_OF_TOTAL_GRAPH_SIZE);
259         if (maxValue != minValue) {
260             point = (int) (height - (height * ((traceData - minValue) / (maxValue - minValue))));
261         }
262         return point;
263     }
264 
265     private boolean hasDataPointToDisplay(){
266     	boolean result = false;
267     	if(leftDataPointsToDisplay != null && leftDataPointsToDisplay.size() > 0){
268     		result = true;
269     	}
270     	return result;
271     }
272 
273     /**
274      * this is where the GDE is referenced and the graph gets initialized for the first time
275      * @param GDE
276      */
277     public void setData(GenericDataElement GDE) {
278         this.GDE = GDE;
279         sizeGraph();
280     }
281 
282     public GenericDataElement getData() {
283         return GDE;
284     }
285     /**
286      * used for InfoLayer to get the data from the GraphLayers for data under the mouse
287      * needs to be rewritten.
288      * @param i
289      * @return Double representation of info at the mouse pointer
290      */
291     public Double getMouseInfo(int cursorDistanceFromCenter) {
292     	double graphPosition = OpenLogViewerApp.getInstance().getEntireGraphingPanel().getGraphPosition();
293     	int zoom = OpenLogViewerApp.getInstance().getEntireGraphingPanel().getZoom();
294     	double offset = (graphPosition % 1) * zoom;
295     	cursorDistanceFromCenter += (int)offset;
296     	double numSnapsFromCenter = ((double)cursorDistanceFromCenter / (double)zoom);
297     	numSnapsFromCenter = Math.round(numSnapsFromCenter);
298     	int cursorPosition = (int)graphPosition + (int)numSnapsFromCenter;
299         if (cursorPosition >= 0 && cursorPosition < GDE.size()) {
300             return GDE.get(cursorPosition);
301         } else {
302             return -1.0;
303         }
304     }
305     /**
306      *
307      * @return GDE.getColor()
308      */
309     public Color getColor() {
310         return GDE.getColor();
311     }
312     /**
313      * setter
314      * @param c
315      */
316     public void setColor(Color c) {
317         GDE.setColor(c);
318     }
319     /**
320      * initialize the graph when the width of the graph parent changes or any time a major update happens
321      * such as changing current
322      */
323     public void initGraph() {
324         if (GDE != null) {
325         	leftDataPointsToDisplay = new LinkedList<Double>();
326         	rightDataPointsToDisplay = new LinkedList<Double>();
327         	int graphPosition = (int)OpenLogViewerApp.getInstance().getEntireGraphingPanel().getGraphPosition();
328         	int zoom = OpenLogViewerApp.getInstance().getEntireGraphingPanel().getZoom();
329         	int numPointsThatFitInDisplay = this.getWidth() / zoom;
330         	numPointsThatFitInDisplay += 6; //Add six data points for off-screen (not just two, because of zoom stupidity)
331         	int halfNumPoints = numPointsThatFitInDisplay / 2;
332         	int leftGraphPosition = graphPosition - halfNumPoints;
333         	int rightGraphPosition = graphPosition + halfNumPoints;
334         	for(int i = graphPosition; i > leftGraphPosition; i--){
335         		if(i >= 0 && i < GDE.size()){
336         			leftDataPointsToDisplay.add(GDE.get(i));
337         		}
338         	}
339         	for(int i = graphPosition; i < rightGraphPosition; i++){
340         		if (i >= 0 && i < GDE.size()){
341         			rightDataPointsToDisplay.add(GDE.get(i));
342         		}
343         	}
344         }
345     }
346     /**
347      * maintains the size of the graph when applying divisions
348      */
349     public void sizeGraph() {
350         MultiGraphLayeredPane lg = OpenLogViewerApp.getInstance().getMultiGraphLayeredPane();
351         int wherePixel = 0 ;
352         if (lg.getTotalSplits() > 1) {
353             if (GDE.getSplitNumber() <= lg.getTotalSplits()) {
354                 wherePixel += lg.getHeight() / lg.getTotalSplits() * GDE.getSplitNumber() - (lg.getHeight() / lg.getTotalSplits());
355             } else {
356                 wherePixel += lg.getHeight() / lg.getTotalSplits() * lg.getTotalSplits() - (lg.getHeight() / lg.getTotalSplits());
357             }
358         }
359 
360         this.setBounds(0, wherePixel, lg.getWidth(), lg.getHeight() / (lg.getTotalSplits()));
361         initGraph();
362     }
363 
364     /**
365      * Graph total size
366      * @return GDE.size()
367      */
368     public int graphSize() {
369         return GDE.size();
370     }
371 
372 }