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 "AS IS", 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