View Javadoc

1   /*
2    * Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton,
3    * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
4    *
5    * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
6    * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
7    * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
8    * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
9    * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
10   * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
11   * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
12   * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
13   * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
14   * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
15   * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
16   * OR MODIFICATIONS.
17   * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
18   * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
19   * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
20   * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
21   */
22  
23  package de.desy.acop.displayers.chart;
24  
25  import java.awt.Color;
26  import java.beans.Beans;
27  import java.beans.PropertyChangeSupport;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Map;
31  
32  import javax.swing.SwingUtilities;
33  
34  import com.cosylab.gui.displayers.CommonDisplayer;
35  import com.cosylab.gui.displayers.Displayer;
36  import com.cosylab.util.CommonException;
37  
38  import de.desy.acop.displayers.AcopTrendChart;
39  import de.desy.acop.displayers.selector.SelectorUtilities;
40  import de.desy.acop.displayers.tools.AcopGraphHistoryParameters;
41  import de.desy.acop.displayers.tools.AcopGraphParameters;
42  
43  /**
44   * <code>AcopTrendChartConsumer</code> is a consumer used by <code>AcopTrendChart</code>
45   * Each consumer can present one trend line in the chart which shows
46   * the time evolution of some remote property. Consumers can be added 
47   * or removed from the chart in runtime. This consumer works with 
48   * <code>AcopGraphHistoryParameters</code> which specifiy all the properties 
49   * required to customize a particular trend line.
50   * 
51   * @author Tilen Kusterle, Cosylab
52   * @see AcopTrendChart
53   * @see AcopGraphHistoryParameters
54   */
55  public class AcopTrendChartConsumer extends AcopChartConsumer {
56  
57  	public static final String SEQUENCE_LENGTH = "sequenceLength";
58  	public static final String SEQUENCE_INDEX = "sequenceIndex";
59  	// TODO AcopConst.max_points is protected!
60  	public static final int MAX_POINTS = 524288;
61  	
62  	private PropertyChangeSupport propertyChangeSupport;
63  	
64  	private int sequenceLength = 0;
65  	private int sequenceIndex = 0;
66  	
67  	private int plotLength = 0;
68  	private double maxHistoryTimestamp = 0;
69  	private HistoryDataConverter historyDataConverter;
70  	
71  	private boolean normalized = false;
72  	
73  	private double[] visibleYData;
74  		
75  	/**
76  	 * Constructs a new AcopTrendChartConsumer.
77  	 * 
78  	 * @param acopTrendChart parent chart
79  	 * @param name the name of the consumer
80  	 */
81  	public AcopTrendChartConsumer(AcopTrendChart acopTrendChart, String name) {
82  		super(acopTrendChart, name);
83  		super.setTrendChart(true);
84  		xData = new double[0];
85  		yData = new double[0];
86  		visibleYData = new double[0];
87  		historyDataConverter = new HistoryDataConverter();
88  	}
89  	
90  	protected void updateChart() {
91  		if (acopChartReorg == null) return;
92  		
93  		Runnable r = new Runnable(){
94  			public void run() {
95  				trimData();
96  				prepareYData();
97  				
98  				synchronized(acopChartReorg) {
99  					if (acopChartReorg == null) return;
100 					if (displayHandle < 0) {
101 						applyParameters();
102 					    displayHandle = acopChartReorg.getAcop().draw(visibleYData,xData);//,disable,xLabels,arraySize,maxNumber);
103 					    plotLength = xData.length;
104 					}
105 					else {
106 						if (plotLength == xData.length) {
107 							acopChartReorg.getAcop().refreshScreen(visibleYData, displayHandle, arraySize, 0, xData);
108 						}
109 						else reinitializePlot();
110 					}
111 					getAcopChart().autoScale();
112 				}				
113 			}
114 		};
115 		if (SwingUtilities.isEventDispatchThread()) {
116 			r.run();
117 		} else {
118 			SwingUtilities.invokeLater(r);
119 		}
120 		
121 	}
122 
123 	/* (non-Javadoc)
124 	 * @see de.desy.acop.displayers.tools.AcopChartReorgConsumer#setCharacteristics(java.util.Map)
125 	 */
126 	@Override
127 	public void setCharacteristics(Map characteristics) {
128 
129 		if (acopChartReorg == null) {
130 			return;
131 		}
132 
133 		Object o1 = null;
134 		Object o2 = null;
135 
136 		o1 = characteristics.get(CommonDisplayer.C_GRAPH_MAX);
137 
138 		if (o1 == null) {
139 			o1 = characteristics.get(CommonDisplayer.C_MAXIMUM);
140 		}
141 
142 		o2 = characteristics.get(CommonDisplayer.C_GRAPH_MIN);
143 
144 		if (o2 == null) {
145 			o2 = characteristics.get(CommonDisplayer.C_MINIMUM);
146 		}
147 
148 		if (o1 != null || o2 != null) {
149 			double max = o1 != null ? ((Number)o1).doubleValue()
150 				: acopChartReorg.getYRangeMax();
151 			double min = o2 != null ? ((Number)o2).doubleValue()
152 				: acopChartReorg.getYRangeMin();
153 			
154 			setPreferredYMax(max);
155 			setPreferredYMin(min);
156 			acopChartReorg.updateYScale(this);
157 		}
158 
159 		o1 = characteristics.get(CommonDisplayer.C_DISPLAY_NAME);
160 		
161 		String newName = (String) o1;
162 		if (newName != null && !newName.equals(acopChartReorg.getTitle())) {
163 			// TODO why was this here?
164 //		    acopChart2.getAcop().setName(newName);
165 		    acopChartReorg.setTitle(newName);
166 		}
167 		
168 		o1 = characteristics.get(Displayer.C_UNITS);
169 		if (o1 != null) {
170 			acopChartReorg.getAcop().setYAxisLabel((String)o1);
171 		}
172 
173 		//avoid setting colors through characterstics to avoid different threads
174 		//setting the same property at the same time - consumer might end up with wrong color
175 		o1 = characteristics.get(Displayer.C_COLOR);
176 
177 		if (o1 != null) {
178 			color = (Color) o1;
179 			acopChartReorg.getAcop().setForeground(color);
180 		}
181 			
182 		o1 = characteristics.get("xLabels");
183 		
184 		o1 = characteristics.get(CommonDisplayer.C_SEQUENCE_LENGTH);
185 		setSequenceLength((Integer) o1);
186 		
187 		if (!Beans.isDesignTime()) {
188 			acopChartReorg.updateXScale(this);
189 		}
190 		acopChartReorg.repaint();
191 	}
192 
193 	/* (non-Javadoc)
194 	 * @see de.desy.acop.displayers.tools.AcopChartReorgConsumer#setDisplayerParameters(de.desy.acop.displayers.tools.AcopGraphParameters)
195 	 */
196 	@Override
197 	public void setDisplayerParameters(AcopGraphParameters displayerParameters) {
198 		this.displayerParameters = displayerParameters;
199 		this.color = displayerParameters.getColor() != null ? displayerParameters.getColor() : acopChartReorg.getAcop().getForeground();
200 		this.width = displayerParameters.getWidth();
201 		this.dispalyMode = displayerParameters.getMode() > -1 ? displayerParameters.getMode() : acopChartReorg.getAcop().getDisplayMode();
202 		setSequenceLength(SelectorUtilities.getSequenceLength(displayerParameters.getConnectionParameters()));
203 		setSequenceIndex(displayerParameters.getArrayIndex());
204 		historyDataConverter.useConverter(displayerParameters.getConverter());
205 	}
206 
207 	private long lastLocalTimestamp;
208 	private long lastDrawnTimestamp;
209 	private long lastNow;
210 	/* (non-Javadoc)
211 	 * @see de.desy.acop.displayers.tools.AcopChartReorgConsumer#updateValue(long, double[])
212 	 */
213 	@Override
214 	public void updateValue(long timestamp, double[] value) throws CommonException {
215 		long now = System.currentTimeMillis();
216 		if (lastTimestamp != timestamp) {
217 			lastTimestamp = timestamp;
218 			lastLocalTimestamp = now;
219 		} else {
220 			timestamp = lastTimestamp + now - lastLocalTimestamp;
221 		}
222 		if (lastDrawnTimestamp > timestamp) {
223 			timestamp =  lastDrawnTimestamp + now - lastNow;
224 		}
225 		
226 //		lastTimestamp=timestamp;
227 		setSequenceLength(value.length);
228 		((AcopTrendChart) acopChartReorg).setLastTimestamp(timestamp/1000.);
229 		appendPoint(timestamp/1000., value[getSequenceIndex()]);
230 		lastDrawnTimestamp = timestamp;
231 		lastNow = now;
232 		updateChart();
233 	}
234 
235 	/* (non-Javadoc)
236 	 * @see de.desy.acop.displayers.tools.AcopChartReorgConsumer#isTrendChart()
237 	 */
238 	@Override
239 	public boolean isTrendChart() {
240 		return true;
241 	}
242 
243 	/* (non-Javadoc)
244 	 * @see de.desy.acop.displayers.tools.AcopChartReorgConsumer#setTrendChart(boolean)
245 	 */
246 	@Override
247 	public void setTrendChart(boolean isTrendChart) {
248 		// TODO do nothing or some notification?
249 	}
250 	
251 	private void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
252 		getPropertyChangeSupport().firePropertyChange(propertyName, oldValue, newValue);
253 	}
254 
255 	private PropertyChangeSupport getPropertyChangeSupport() {
256 		if (propertyChangeSupport == null) {
257 			propertyChangeSupport = new PropertyChangeSupport(this);
258 		}
259 		return propertyChangeSupport;
260 	}
261 	
262 	/**
263 	 * Sets the data in the consumer normalized or absolute.
264 	 * When normalizes all data is scaled to be shown as the percentage of
265 	 * its max-min value.
266 	 * 
267 	 * @param normalized true for normalized, false for absolute
268 	 */
269 	public void setNormalizedData(boolean normalized) {
270 		if (this.normalized == normalized) return;
271 		this.normalized = normalized;
272 		firePropertyChange("normalizedData", !normalized, normalized);
273 		updateChart();
274 	}
275 	
276 	/**
277 	 * Returns true if this consumer shows data as normalized.
278 	 * 
279 	 * @return
280 	 */
281 	public boolean isNormalizedData() {
282 		return normalized;
283 	}
284 
285 	private void trimData() {
286 		if (acopChartReorg == null) return;
287 		synchronized(acopChartReorg) {
288     		if (!((AcopTrendChart) acopChartReorg).isAutoScaleToLiveData()) {
289     			int index;
290     			double presentTime = ((AcopTrendChart) acopChartReorg).getLastTimestamp();
291     			double timeSpan = ((AcopTrendChart) acopChartReorg).getTimeSpan();
292     			double startTime = presentTime-timeSpan;
293     			for (index = 0; index < xData.length; index++) {
294     				if (xData[index] > startTime) {
295     					break;
296     				}
297     			}
298     			if (index != 0) {
299     				if (index > xData.length) index = xData.length;
300     				double[] xDataCopy =  Arrays.copyOf(xData, xData.length);
301     				double[] yDataCopy =  Arrays.copyOf(yData, yData.length);
302     				xData = new double[xData.length-index];
303     				yData = new double[yData.length-index];
304     				System.arraycopy(xDataCopy, index, xData, 0, xDataCopy.length-index);
305     				System.arraycopy(yDataCopy, index, yData, 0, yDataCopy.length-index);
306     			}
307     		}
308     		if (xData.length > MAX_POINTS) {
309     			int i;
310     			for (i = 0; i < xData.length; i++) {
311     				if (xData[i] > maxHistoryTimestamp) {
312     					i--;
313     					break;
314     				}
315     			}
316     			if (i < 0 || i > MAX_POINTS) i = 0;
317     			double[] xDataCopy =  Arrays.copyOf(xData, xData.length);
318     			double[] yDataCopy =  Arrays.copyOf(yData, yData.length);
319     			xData = new double[MAX_POINTS];
320     			yData = new double[MAX_POINTS];
321     			System.arraycopy(xDataCopy, 0, xData, 0, i);
322     			System.arraycopy(yDataCopy, 0, yData, 0, i);
323     			System.arraycopy(xDataCopy, xDataCopy.length-(MAX_POINTS-i), xData, i, MAX_POINTS-i);
324     			System.arraycopy(yDataCopy, yDataCopy.length-(MAX_POINTS-i), yData, i, MAX_POINTS-i);
325     			acopChartReorg.autoScaleXOnce();
326     		}
327 		}
328 		
329 	}
330 	
331 	private void prepareYData() {
332 		synchronized(acopChartReorg) {
333     		if (isNormalizedData()) {
334     			visibleYData = new double[yData.length];
335     			double min = getPreferredYMin();
336     			double scale = (getPreferredYMax() - min)/100.;
337     			for (int i = 0; i < visibleYData.length; i++) {
338     				visibleYData[i] = (yData[i] - min)/scale;
339     			}
340     		} else {
341     			visibleYData = yData;
342     		}
343 		}
344 	}
345 	
346 	private void mergeData(double[][] historyData) {
347 		synchronized(acopChartReorg) {
348     		ArrayList<Double> xList = new ArrayList<Double>();
349     		ArrayList<Double> yList = new ArrayList<Double>();
350     		boolean skip = false;
351     		for (int i = 0; i < historyData[0].length; i++) {
352     			double t = historyData[0][i];
353     			for (int j = 0; j < xList.size(); j++) {
354     				if (xList.get(j) >= t) {
355     					if (xList.get(j) > t) {
356     						xList.add(j, t);
357     						yList.add(j, historyData[1][i]);
358     					}
359     					skip = true;
360     					break;
361     				}
362     			}
363     			if (skip) {
364     				skip = false;
365     				continue;
366     			}
367     			xList.add(t);
368     			yList.add(historyData[1][i]);
369     		}
370     		for (int i = 0; i < xData.length; i++) {
371     			double t = xData[i];
372     			for (int j = 0; j < xList.size(); j++) {
373     				if (xList.get(j) >= t) {
374     					if (xList.get(j) > t) {
375     						xList.add(j, t);
376     						yList.add(j, yData[i]);
377     					}
378     					skip = true;
379     					break;
380     				}
381     			}
382     			if (skip) {
383     				skip = false;
384     				continue;
385     			}
386     			xList.add(t);
387     			yList.add(yData[i]);
388     		}
389     		if (historyData[0].length > 0) maxHistoryTimestamp = historyData[0][historyData[0].length-1];
390     		xData = new double[xList.size()];
391     		yData = new double[yList.size()];
392     		for (int i = 0; i < xList.size(); i++) {
393     			xData[i] = xList.get(i);
394     			yData[i] = yList.get(i);
395     		}
396 		}
397 	}
398 	
399 	private void appendPoint(double x, double y) {
400 		synchronized (acopChartReorg) {
401     		double[] xDataCopy =  Arrays.copyOf(xData, xData.length);
402     		double[] yDataCopy =  Arrays.copyOf(yData, yData.length);
403     		xData = new double[xData.length+1];
404     		yData = new double[yData.length+1];
405     		System.arraycopy(xDataCopy, 0, xData, 0, xDataCopy.length);
406     		System.arraycopy(yDataCopy, 0, yData, 0, yDataCopy.length);
407     		xData[xData.length-1] = x;
408     		yData[yData.length-1] = y;
409 		}
410 	}
411 
412 	/* (non-Javadoc)
413 	 * @see de.desy.acop.displayers.tools.AcopChartReorgConsumer#dumpData()
414 	 */
415 	@Override
416 	public double[][] dumpData() {
417 		double[][] clone = new double[2][xData.length];
418 		System.arraycopy(xData, 0, clone[0] ,0,xData.length);
419 		System.arraycopy(yData, 0, clone[1] ,0,xData.length);
420 		return clone;
421 	}
422 
423 	/**
424 	 * Returns the sequence index.
425 	 * @return the sequenceIndex
426 	 */
427 	public int getSequenceIndex() {
428 		return sequenceIndex;
429 	}
430 
431 	/**
432 	 * Sets the sequence index.
433 	 * @param sequenceIndex the sequenceIndex to set
434 	 */
435 	public void setSequenceIndex(int sequenceIndex) {
436 		if (this.sequenceIndex == sequenceIndex) return;
437 		int oldIndex = getSequenceIndex();
438 		if (sequenceIndex >= getSequenceLength() || sequenceIndex < 0) {
439 			sequenceIndex = 0;
440 		}
441 		this.sequenceIndex = sequenceIndex;
442 		firePropertyChange(SEQUENCE_INDEX, oldIndex, getSequenceIndex());
443 	}
444 
445 	/**
446 	 * Returns the length of the sequence.
447 	 * @return the sequenceLength
448 	 */
449 	public int getSequenceLength() {
450 		return sequenceLength;
451 	}
452 
453 	/**
454 	 * Sets the length of the sequence.
455 	 * @param sequenceLength the sequenceLength to set
456 	 */
457 	public void setSequenceLength(int sequenceLength) {
458 		if (this.sequenceLength == sequenceLength) return;
459 		int oldLength = getSequenceLength();
460 		this.sequenceLength = sequenceLength;
461 		if (getSequenceIndex() >= getSequenceLength()) {
462 			setSequenceIndex(0);
463 		}
464 		firePropertyChange(SEQUENCE_LENGTH, oldLength, getSequenceLength());
465 	}
466 
467 	/**
468 	 * Returns the history parameters if this graph includes history data.
469 	 * 
470 	 * @return the history parameters
471 	 */
472 	public HistoryParameters getHistoryParameters() {
473 		AcopGraphParameters parameters = getDisplayerParameters();
474 		if (parameters instanceof AcopGraphHistoryParameters) return ((AcopGraphHistoryParameters) parameters).getHistoryParameters();
475 		return null;
476 	}
477 
478 	/**
479 	 * Sets the history data for this graph. This first array holds the 
480 	 * timestamp and the second array holds the values at those timestamps.
481 	 * 
482 	 * @param historyData data array: historyData[0] holds timestamp and historyData[1]
483 	 * 				holds values
484 	 */
485 	public void loadHistoryData(double[][] historyData) {
486 		double[] convertedHistoryData = historyDataConverter.convertHistoryData(historyData[1]);
487 		historyData[1] = convertedHistoryData;
488 		mergeData(historyData);
489 		updateChart();
490 	}
491 	
492 }