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