001    package org.LiveGraph.gui.plot;
002    
003    import java.awt.BorderLayout;
004    import java.awt.Color;
005    import java.awt.Dimension;
006    import java.awt.Graphics;
007    import java.awt.GridLayout;
008    import java.awt.Point;
009    import java.awt.event.MouseAdapter;
010    import java.awt.event.MouseEvent;
011    import java.awt.event.MouseMotionAdapter;
012    import java.awt.geom.Point2D;
013    import java.util.ArrayList;
014    import java.util.Collections;
015    import java.util.List;
016    
017    import javax.swing.BorderFactory;
018    import javax.swing.JLabel;
019    import javax.swing.JPanel;
020    import javax.swing.Popup;
021    import javax.swing.PopupFactory;
022    import javax.swing.border.EtchedBorder;
023    
024    import org.LiveGraph.LiveGraph;
025    import org.LiveGraph.dataCache.CacheEvent;
026    import org.LiveGraph.dataCache.DataCache;
027    import org.LiveGraph.events.Event;
028    import org.LiveGraph.events.EventType;
029    import org.LiveGraph.gui.LiveGraphSettingsPanel;
030    import org.LiveGraph.plot.Plotter;
031    import org.LiveGraph.settings.SettingsEvent;
032    
033    import com.softnetConsult.utils.collections.ReadOnlyIterator;
034    import com.softnetConsult.utils.swing.SwingTools;
035    
036    /**
037     * The plotter panel of the application. This is the only component contained in
038     * the content pane of the application's plot window. API users may request
039     * {@link org.LiveGraph.gui.GUIManager} to create additional instances of a
040     * {@code PlotPanel} if they wish to integrate the LiveGraph GUI into their application.
041     * 
042     * <p>
043     *   <strong>LiveGraph</strong>
044     *   (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>).
045     * </p> 
046     * <p>Copyright (c) 2007-2008 by G. Paperin.</p>
047     * <p>File: PlotPanel.java</p>
048     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
049     *    without modification, are permitted provided that the following terms and conditions are met:
050     * </p>
051     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
052     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
053     *    this list of conditions and the following disclaimer.<br />
054     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
055     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
056     *    and the following disclaimer in the documentation and/or other materials provided with
057     *    the distribution.<br />
058     *    3. All advertising materials mentioning features or use of this software or any derived
059     *    software must display the following acknowledgement:<br />
060     *    <em>This product includes software developed by the LiveGraph project and its
061     *    contributors.<br />(http://www.live-graph.org)</em><br />
062     *    4. All advertising materials distributed in form of HTML pages or any other technology
063     *    permitting active hyper-links that mention features or use of this software or any
064     *    derived software must display the acknowledgment specified in condition 3 of this
065     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
066     *    homepage (http://www.live-graph.org).
067     * </p>
068     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
069     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
070     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
071     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
072     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
073     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
074     * </p>
075     * 
076     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
077     * @version {@value org.LiveGraph.LiveGraph#version}
078     *
079     */
080    public class PlotPanel extends LiveGraphSettingsPanel {
081    
082    private JLabel statusLabel = null;
083    private Plotter plotter = null;
084    private JPanel canvas = null;
085    private boolean highlightDataPoints = true;
086    private Popup hlSerPopup = null; 
087    private List<String> seriesLabels = null;
088    
089    /**
090     * Creates and setts up a plotter panel.
091     * 
092     * @param plotter The plotter object for this window.
093     */
094    public PlotPanel(Plotter plotter) {
095            super();
096            
097            if (null == plotter)
098                    throw new NullPointerException("Cannot use a null plotter to construct a PlotPanel");
099            
100            this.plotter = plotter;
101            this.highlightDataPoints = true;
102            if (null != LiveGraph.application().getGraphSettings())
103                    this.highlightDataPoints = LiveGraph.application().getGraphSettings().getHighlightDataPoints();
104            this.seriesLabels = new ArrayList<String>();
105            this.initialize();
106    }
107    
108    /**
109     * This method initializes the panel.
110     */
111    private void initialize() {
112            
113            // General settings:
114    
115            Dimension panelDim = new Dimension(450, 500);
116            this.setPreferredSize(panelDim);
117            this.setSize(panelDim);
118            this.setLayout(new BorderLayout());
119            
120            JPanel panel = null;
121            
122            // Ststus label:
123            
124            statusLabel = new JLabel();
125            statusLabel.setFont(SwingTools.getPlainFont(statusLabel));
126            
127            panel = new JPanel(new BorderLayout());
128            
129            panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5, 3, 5, 5),
130                                                                                                               BorderFactory.createEtchedBorder(EtchedBorder.RAISED)));     
131            panel.setPreferredSize(new Dimension(panelDim.width, 30));
132            panel.add(statusLabel, BorderLayout.CENTER);
133            
134            this.add(panel , BorderLayout.SOUTH);
135            
136            // Plot canvas: 
137            
138            hlSerPopup = null; 
139            canvas = new JPanel() {
140                    @Override public void paint(Graphics g) {
141                            super.paint(g);
142                            plotter.paint(g);
143                    }               
144                    @Override public void setBounds(int x, int y, int width, int height) {
145                            super.setBounds(x, y, width, height);
146                            plotter.setScreenSize(width, height);
147                    }
148            };
149            canvas.setBackground(Color.WHITE);
150            canvas.addMouseListener(new MouseAdapter() {
151                    @Override public void mouseExited(MouseEvent e) {
152                            setStatusMessage("");
153                            if (highlightDataPoints) {
154                                    List<Integer> l = Collections.emptyList();
155                                    LiveGraph.application().guiManager().dataSeriesHighlighted(l);
156                            }
157                    }
158                    @Override public void mouseReleased(MouseEvent e) {
159                            if (highlightDataPoints && MouseEvent.BUTTON1 == e.getButton() && null != hlSerPopup) {
160                                    hlSerPopup.hide();
161                                    hlSerPopup = null;
162                            }
163                    }
164                    @Override public void mousePressed(MouseEvent e) {
165                            if (highlightDataPoints && MouseEvent.BUTTON1 == e.getButton())
166                                    showHlSeriesPopup(plotter.highlightAround(e.getPoint()), e.getXOnScreen(), e.getYOnScreen());
167                    }
168            });
169            canvas.addMouseMotionListener(new MouseMotionAdapter() {
170                    @Override public void mouseDragged(MouseEvent e) { this.mouseMoved(e); }
171                    @Override public void mouseMoved(MouseEvent e) {
172                            if (plotter.screenTooSmall()) {
173                                    setStatusMessage("Enlarge this window.");
174                                    return;
175                            }
176                            if (!plotter.getShowAtLeastOneSeries()) {
177                                    setStatusMessage("No data selected for display.");
178                                    return;
179                            } 
180                            Point ep = e.getPoint();
181                            Point2D.Double dp = plotter.screenToDataPoint(ep);
182                            setStatusMessage(String.format("(%.3f, %.3f)", dp.x, dp.y));
183                            if (highlightDataPoints) {
184                                    List<Integer> hlSeries = plotter.highlightAround(ep);
185                                    LiveGraph.application().guiManager().dataSeriesHighlighted(hlSeries);                   
186                                    canvas.repaint();
187                                    if (MouseEvent.MOUSE_DRAGGED == e.getID() && null != hlSerPopup)
188                                            showHlSeriesPopup(hlSeries, e.getXOnScreen(), e.getYOnScreen());
189                            }
190                    }
191            });
192            
193            panel = new JPanel(new BorderLayout());
194            panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5, 5, 3, 5),
195                                                                                                               BorderFactory.createEtchedBorder(EtchedBorder.RAISED)));
196            panel.add(canvas, BorderLayout.CENTER);
197            this.add(panel, BorderLayout.CENTER);
198    } // private void initialize()
199    
200    
201    /**
202     * Shows the popup with the labels of highlighted data series.
203     * 
204     * @param hlSeries List of indices of highlighted data series.
205     * @param mx Current mouse position on screen (x).
206     * @param my Current mouse position on screen (y).
207     */
208    private void showHlSeriesPopup(List<Integer> hlSeries, int mx, int my) {
209                    
210            JPanel panel = new JPanel(new GridLayout(hlSeries.size(), 1, 2, 2));
211            panel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
212            JLabel label = null;
213            
214            if (hlSeries.isEmpty()) {
215                    
216                    label = new JLabel("- no data series highlighted -");
217                    panel.add(label);
218                    
219            } else {
220                    synchronized(seriesLabels) {
221                            for (int s : hlSeries) {
222                                    
223                                    if (s >= seriesLabels.size())
224                                            continue;
225                                    
226                                    label = new JLabel(seriesLabels.get(s));
227                                    label.setFont(SwingTools.getPlainFont(label));
228                                    label.setForeground(LiveGraph.application().getDataSeriesSettings().getColour(s));
229                                    panel.add(label);       
230                            }
231                    }
232            }
233            
234            
235            if (null != hlSerPopup)
236                    hlSerPopup.hide();                                                      
237            hlSerPopup = PopupFactory.getSharedInstance().getPopup(canvas, panel, mx + 15, my + 15);
238            hlSerPopup.show();
239    }
240    
241    /**
242     * Update the status bar message.
243     * @param msg message.
244     */
245    public void setStatusMessage(String msg) {
246            if (null == msg)
247                    return;
248            statusLabel.setText(msg);
249    }
250    
251    /**
252     * Updates the series labels from the specified iterator.
253     * @param labels Series labels.
254     */
255    public void setSeriesLabels(ReadOnlyIterator<String> labels) {
256            
257            synchronized(seriesLabels) {
258                    
259                    seriesLabels.clear();
260                    
261                    if (null == labels)
262                            return;
263                    
264                    while (labels.hasNext())
265                            seriesLabels.add(labels.next());
266            }
267    }
268    
269    /**
270     * Processes events.
271     * 
272     * @param event Event to process.
273     */
274    @Override
275    public void eventRaised(Event<? extends EventType> event) {
276            
277            super.eventRaised(event);
278            
279            if (event.getDomain() == CacheEvent.class) {
280                    processCacheEvent(event.cast(CacheEvent.class));
281                    return;
282            }
283    }
284    
285    /**
286     * When the application's settings change, this method is called in order
287     * to update the GUI accordingly.<br/>
288     * - Repaints the plot canvas when the data file settings change.<br />
289     * - Repaints the plot canvas when the graph settings change.<br />
290     * 
291     * @param event Describes the change event.
292     */
293    @Override
294    protected void processSettingsEvent(Event<SettingsEvent> event) {
295    
296            switch(event.getType()) {
297                    
298                    case GS_HighlightDataPoints:
299                            highlightDataPoints = event.getInfoBoolean();
300                            // no break intended here.
301                            
302                    case GS_Load:
303                    case GS_Save:
304                    case GS_MinY:
305                    case GS_MaxY:
306                    case GS_MinX:
307                    case GS_MaxX:
308                    case GS_VGridType:
309                    case GS_VGridSize:
310                    case GS_VGridColour:
311                    case GS_HGridType:
312                    case GS_HGridSize:
313                    case GS_HGridColour:
314                    case GS_XAxisType:
315                    case GS_XAxisSeriesIndex:
316                    case GS_XAxisParamValue:
317                            
318                    case DSS_Load:
319                    case DSS_Save:
320                    case DSS_SeriesRange_Visibility:
321                    case DSS_Series_Visibility:
322                    case DSS_Series_Colour:
323                    case DSS_Series_TransformMode:
324                    case DSS_Series_TransformParam:
325                            
326                            canvas.repaint();
327                            break;
328                            
329                    default:
330                            break;
331            }       
332    }
333    
334    /**
335     * Repaints the plot canvas when the cache was updated.
336     * 
337     * @param event The cache event.
338     */
339    private void processCacheEvent(Event<CacheEvent> event) {
340            
341            switch(event.getType()) {
342                    
343                    case CACHE_UpdatedData:
344                            canvas.repaint();
345                            break;
346                            
347                    case CACHE_UpdatedLabels:
348                            DataCache cache = (DataCache) event.getProducer();
349                            synchronized (cache) {
350                                    setSeriesLabels(cache.iterateDataSeriesLabels());
351                            }
352                            break;
353                    
354                    default:
355                            break;                  
356            }
357    }
358    
359    }  // public class PlotPanel