/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.display; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import javax.swing.JFrame; import javax.swing.WindowConstants; import javax.swing.table.AbstractTableModel; import org.opensourcephysics.controls.XML; import org.opensourcephysics.controls.XMLControl; import org.opensourcephysics.controls.XMLLoader; import org.opensourcephysics.display.axes.XAxis; import org.opensourcephysics.display.axes.XYAxis; /** * ComplexDataset stores and plots a complex dataset (x,z) where the dependent variable * has real and imaginary parts, z=(real, imaginary). * * In Re_Im mode, both the real and imaginary parts are shown as separate curves. * In Phase mode, the vertical coordinate represents magnitude and color represents phase. * ComplexDataset is Drawable and can be rendered on a DrawingPanel. * ComplexDataset extends AbstractTableModel and can be rendered in a JTable. * * @author Wolfgang Christian * @version 1.0 */ public class ComplexDataset extends AbstractTableModel implements Drawable, Measurable, Data { static final double PI2 = Math.PI*2; /** AMP height equal to |z|.. */ public static final int AMP_CURVE = 0; // marker type /** RE_IM real and imaginary curves. */ public static final int RE_IM_CURVE = 1; // marker type /** PHASE_CURVE the phase is shown as color. */ public static final int PHASE_CURVE = 2; // marker type /** PHASE_BAR the phase is shown as the bar's color */ public static final int PHASE_BAR = 3; // marker type /** Field POST */ public final static int PHASE_POST = 4; // marker type /** visible in drawing panel */ protected boolean visible = true; /** affect autoscaled drawing panels */ protected boolean measurable = true; protected double[] xpoints; // array of x points protected double[] re_points; // array of y points protected double[] im_points; // array of y points protected double[] amp_points; // array of amplitude points protected int index; // the current index of the array private int markerShape = PHASE_CURVE; private int markerSize = 5; // the size in pixels of the marker private boolean centered = true; // center the y values on the x axis. // private boolean showAmp = true; // Separate Real and Imaginary curves // private boolean showReIm = false; // Separate Real and Imaginary curves private boolean showPhase = true; // Show phase as color private double xmin; // the minimum x value in the dataset private double xmax; // the maximum x value in the dataset private double ampmin; // the minimum x value in the dataset private double ampmax; // the maximum x value in the dataset private double remax; // the maximum real value in the dataset private double remin; // the minimum real value in the dataset private double immax; // the maximum real value in the dataset private double immin; // the minimum real value in the dataset private boolean sorted = false; // sort the data by increasing x private boolean connected = true; private int initialSize; // the initial size of the array // private Color reColor=Color.red; // the color of the real data line // private Color imColor=Color.blue; // the color of the real data line private Color lineColor = Color.black; private GeneralPath ampPath; // used to draw line plots private Trail reTrail = new Trail(), imTrail = new Trail(); private String name = "Complex Data"; //$NON-NLS-1$ private String xColumnName = "x"; // the name of the x data //$NON-NLS-1$ private String reColumnName = "re"; // the name of the y data //$NON-NLS-1$ private String imColumnName = "im"; // the name of the y data //$NON-NLS-1$ private int stride = 1; // the data stride in table view private AffineTransform flip; Dataset reDataset; Dataset imDataset; int datasetID = hashCode(); /** * Dataset constructor. */ public ComplexDataset() { reTrail.color = Color.RED; imTrail.color = Color.BLUE; initialSize = 10; xColumnName = "x"; //$NON-NLS-1$ reColumnName = "re"; //$NON-NLS-1$ imColumnName = "im"; //$NON-NLS-1$ ampPath = new GeneralPath(); index = 0; flip = new AffineTransform(1, 0, 0, -1, 0, 0); clear(); } /** * Shows the phase legend. */ public JFrame showLegend() { InteractivePanel panel = new InteractivePanel(); panel.setPreferredGutters(5, 5, 5, 25); DrawingFrame frame = new DrawingFrame(DisplayRes.getString("GUIUtils.PhaseLegend"), panel); //$NON-NLS-1$ frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); frame.setJMenuBar(null); panel.addDrawable(new Phase()); XAxis xaxis = new XAxis(DisplayRes.getString("ComplexDataset.Legend.XAxis")); //$NON-NLS-1$ xaxis.setLocationType(XYAxis.DRAW_AT_LOCATION); xaxis.setEnabled(true); // enable the dragging panel.setClipAtGutter(false); panel.addDrawable(xaxis); panel.setSquareAspect(false); panel.setPreferredMinMax(-Math.PI, Math.PI, -1, 1); frame.setSize(300, 120); frame.setVisible(true); return frame; } class Phase implements Drawable { public void draw(DrawingPanel panel, Graphics g) { int w = panel.getWidth()-5+1; int h = panel.getHeight()-25; for(int i = 5; i<w; i++) { double theta = Math.PI*(-1+2*((float) i)/w); Color c = DisplayColors.phaseToColor(theta); g.setColor(c); g.drawLine(i, 5, i, h); } } } /** * Gets the valid measure flag. The measure is valid if the min and max values have been set. * * @return <code>true<\code> if measure is valid */ public boolean isMeasured() { if(index<1) { return false; } return measurable; } /** * Gets the x world coordinate for the left hand side of the panel. * @return xmin */ public double getXMin() { return xmin; } /** * Gets the x world coordinate for the right hand side of the panel. * @return xmax */ public double getXMax() { return xmax; } /** * Gets y world coordinate for the bottom of the panel. * @return ymin */ public double getYMin() { if(markerShape==RE_IM_CURVE) { return -ampmax; } else if(centered&&((markerShape==PHASE_BAR)||(markerShape==PHASE_CURVE))) { return -ampmax/2; } else { return 0; } } /** * Gets y world coordinate for the top of the panel. * @return ymax */ public double getYMax() { if(markerShape==RE_IM_CURVE) { return ampmax; } else if(centered&&((markerShape==PHASE_BAR)||(markerShape==PHASE_CURVE))) { return ampmax/2; } if(markerShape==PHASE_POST) { return 1.1*ampmax; } return ampmax; } /** * Gets a copy of the xpoints array. * @return xpoints[] */ public double[] getXPoints() { double[] temp = new double[index]; System.arraycopy(xpoints, 0, temp, 0, index); return temp; } /** * Gets a copy of the real points array. * @return repoints[] */ public double[] getRePoints() { double[] temp = new double[index]; System.arraycopy(re_points, 0, temp, 0, index); return temp; } /** * Gets a copy of the imaginary points array. * @return impoints[] */ public double[] getImPoints() { double[] temp = new double[index]; System.arraycopy(im_points, 0, temp, 0, index); return temp; } /** * Gets a copy of the ypoints array. * @return ypoints[] */ public double[] getYPoints() { double[] temp = new double[index]; System.arraycopy(amp_points, 0, temp, 0, index); return temp; } /** * Gets a data array containing both x and y values. * * @return a double[index][2] array of data */ public double[][] getPoints() { double[][] temp = new double[index][3]; for(int i = 0; i<index; i++) { temp[i] = new double[] {xpoints[i], re_points[i], im_points[i]}; } return temp; } /** * Sets the data point marker. * Shapes are: * AMP_CURVE * RE_IM_CURVE * PHASE_CURVE * PHASE_BAR * PHASE_POST * * @param _markerShape */ public void setMarkerShape(int _markerShape) { markerShape = _markerShape; } /** * Gets the marker shape. * * @return int */ public int getMarkerShape() { return markerShape; } /** * Gets the marker size. * * @return int */ public int getMarkerSize() { return markerSize; } /** * Sets the marker size. * @param size int */ public void setMarkerSize(int size) { markerSize = size; } /** * Sets the sorted flag. Data is sorted by increasing x. * * @param _sorted <code>true<\code> to sort */ public void setSorted(boolean _sorted) { sorted = _sorted; if(sorted) { insertionSort(); } } /** * Sets the data stride for table view. * * A stride of i will show every i-th point. * * @param _stride */ public void setStride(int _stride) { stride = _stride; stride = Math.max(1, stride); } /** * Gets the sorted flag. * * @return <code>true<\code> if the data is sorted */ public boolean isSorted() { return sorted; } /** * Sets the visibility of this Dataset in a DrawingPanel. * * @param b <code>true<\code> if dataset is visible */ public void setVisible(boolean b) { visible = b; } /** * Gets the visibility of this dataset in the DrawingPanel. * @return boolean */ public boolean getVisible() { return visible; } /** * Sets the measurable property. Measurable objects affect panel autoscaling. * * @param b <code>true<\code> if points are connected */ public void setMeasurable(boolean b) { measurable = b; } /** * Gets the measurable property. * @return boolean */ public boolean getMeasurable() { return measurable; } /** * Sets the data connected flag. Points are connected by straight lines. * @param _connected <code>true<\code> if points are connected */ public void setConnected(boolean _connected) { connected = _connected; } /** * Sets the centered flag. Centered complex numbers are shown extending above * and below the y axis. * * @param _centered <code>true<\code> if data is centered */ public void setCentered(boolean _centered) { centered = _centered; } /** * Gets the data connected flag. * @return <code>true<\code> if points are connected */ public boolean isConnected() { return connected; } /** * Sets the color of the lines connecting data points. * @param _lineColor */ public void setLineColor(Color _lineColor) { lineColor = _lineColor; reTrail.color = lineColor; imTrail.color = lineColor; } /** * Sets the color of the lines connecting data points. * @param reColor the real component color * @param imColor the imaginary component color */ public void setLineColor(Color reColor, Color imColor) { lineColor = reColor; reTrail.color = reColor; imTrail.color = imColor; } /** * Line colors for Data interface. * @return */ public java.awt.Color[] getLineColors() { return new Color[] {lineColor, lineColor}; } /** * Gets the line color. * * @return the line color */ public Color getLineColor() { return lineColor; } /** * Fill colors to Data interface. * @return */ public java.awt.Color[] getFillColors() { return new Color[] {lineColor, lineColor}; } /** * Fill color to use for this data * @return */ public java.awt.Color getFillColor() { return lineColor; } /** * Sets the column names when rendering this dataset in a JTable. * * @param _xColumnName String * @param _reColumnName String * @param _imColumnName String */ public void setXYColumnNames(String _xColumnName, String _reColumnName, String _imColumnName) { xColumnName = TeXParser.parseTeX(_xColumnName); reColumnName = TeXParser.parseTeX(_reColumnName); imColumnName = TeXParser.parseTeX(_imColumnName); } /** * Sets the column names when rendering this dataset in a JTable. * * @param _xColumnName String * @param _reColumnName String * @param _imColumnName String * @param datasetName String */ public void setXYColumnNames(String _xColumnName, String _reColumnName, String _imColumnName, String datasetName) { setXYColumnNames(_xColumnName, _reColumnName, _imColumnName); name = TeXParser.parseTeX(datasetName); } /** * Appends (x, re, im) datum to the Dataset. * * @param x double * @param re double * @param im double */ public void append(double x, double re, double im) { if(Double.isNaN(x)||Double.isInfinite(x)||Double.isNaN(re)||Double.isInfinite(re)||Double.isNaN(im)||Double.isInfinite(im)) { return; } if(index>=xpoints.length) { setCapacity(xpoints.length*2); } xpoints[index] = x; re_points[index] = re; im_points[index] = im; double amp = Math.sqrt(re*re+im*im); // generalPath.append(new Rectangle2D.Double(x, y, 0, 0), true); if(index==0) { ampPath.moveTo((float) x, (float) amp); } else { ampPath.lineTo((float) x, (float) amp); } reTrail.addPoint(x, re); imTrail.addPoint(x, im); xmax = Math.max(x, xmax); xmin = Math.min(x, xmin); remin = Math.min(re, remin); remax = Math.max(re, remax); immin = Math.min(im, immin); immax = Math.max(im, immax); ampmin = Math.min(amp, ampmin); ampmax = Math.max(amp, ampmax); index++; // move the new datum if x is less than the last value. if(sorted&&(index>1)&&(x<xpoints[index-2])) { moveDatum(index-1); // the new datum is out of place to move it recalculatePath(); // System.out.println ("data moved"); } } /** * Appends x, real, and imaginary arrays to the Dataset. * @param _xpoints * @param _repoints * @param _impoints */ public void append(double[] _xpoints, double[] _repoints, double[] _impoints) { if(_xpoints==null) { return; } if((_repoints==null)||(_impoints==null)||(_xpoints.length!=_repoints.length)||(_xpoints.length!=_impoints.length)) { throw new IllegalArgumentException("Array lenghts must be equal to append data."); //$NON-NLS-1$ } boolean badData = false; for(int i = 0; i<_xpoints.length; i++) { if(Double.isNaN(_xpoints[i])||Double.isInfinite(_xpoints[i])||Double.isNaN(_repoints[i])||Double.isInfinite(_repoints[i])||Double.isNaN(_impoints[i])||Double.isInfinite(_impoints[i])) { badData = true; continue; } xmax = Math.max(_xpoints[i], xmax); xmin = Math.min(_xpoints[i], xmin); remin = Math.min(_repoints[i], remin); remax = Math.max(_repoints[i], remax); immin = Math.min(_impoints[i], immin); immax = Math.max(_impoints[i], immax); double amp = Math.sqrt(_repoints[i]*_repoints[i]+_impoints[i]*_impoints[i]); ampmin = Math.min(amp, ampmin); ampmax = Math.max(amp, ampmax); if((index==0)&&(i==0)) { ampPath.moveTo((float) _xpoints[i], (float) amp); } else { ampPath.lineTo((float) _xpoints[i], (float) amp); } reTrail.addPoint(_xpoints[i], _repoints[i]); imTrail.addPoint(_xpoints[i], _impoints[i]); } int pointsAdded = _xpoints.length; int availableSpots = xpoints.length-index; if(pointsAdded>availableSpots) { setCapacity(2*(xpoints.length+pointsAdded)); // FIX ME } System.arraycopy(_xpoints, 0, xpoints, index, pointsAdded); System.arraycopy(_repoints, 0, re_points, index, pointsAdded); System.arraycopy(_impoints, 0, im_points, index, pointsAdded); index += pointsAdded; if(badData) { cleanBadData(); } if(sorted) { insertionSort(); } } /** * Appends x and z data to the Dataset. * * Z array has length twice that of x array. *<PRE> * Re(z) = z[2*i] * Im(z) = z[2*i + 1] *</PRE> * * @param _xpoints * @param _zpoints */ public void append(double[] _xpoints, double[] _zpoints) { if(_xpoints==null) { return; } if((_zpoints==null)||(2*_xpoints.length!=_zpoints.length)) { throw new IllegalArgumentException("Length of z array must be twice the length of the x array."); //$NON-NLS-1$ } boolean badData = false; int pointsAdded = _xpoints.length; int availableSpots = xpoints.length-index; if(pointsAdded>availableSpots) { setCapacity(2*(xpoints.length+pointsAdded)); } for(int i = 0; i<_xpoints.length; i++) { if(Double.isNaN(_xpoints[i])||Double.isInfinite(_xpoints[i])||Double.isNaN(_zpoints[2*i])||Double.isInfinite(_zpoints[2*i])||Double.isNaN(_zpoints[2*i+1])||Double.isInfinite(_zpoints[2*i+1])) { badData = true; continue; } xmax = Math.max(_xpoints[i], xmax); xmin = Math.min(_xpoints[i], xmin); remin = Math.min(_zpoints[2*i], remin); remax = Math.max(_zpoints[2*i], remax); immin = Math.min(_zpoints[2*i+1], immin); immax = Math.max(_zpoints[2*i+1], immax); double amp = Math.sqrt(_zpoints[2*i]*_zpoints[2*i]+_zpoints[2*i+1]*_zpoints[2*i+1]); ampmin = Math.min(amp, ampmin); ampmax = Math.max(amp, ampmax); xpoints[index+i] = _xpoints[i]; re_points[index+i] = _zpoints[2*i]; im_points[index+i] = _zpoints[2*i+1]; if((index==0)&&(i==0)) { ampPath.moveTo((float) _xpoints[i], (float) amp); } else { ampPath.lineTo((float) _xpoints[i], (float) amp); } reTrail.addPoint(_xpoints[i], _zpoints[2*i]); imTrail.addPoint(_xpoints[i], _zpoints[2*i+1]); } index += pointsAdded; if(badData) { cleanBadData(); } if(sorted) { insertionSort(); } } /** * Sets the ID number of this Data. * * @param id the ID number */ public void setID(int id) { datasetID = id; } /** * Returns a unique identifier for this Data. * * @return the ID number */ public int getID() { return datasetID; } private void cleanBadData() { for(int i = 0; i<index; i++) { if(Double.isNaN(xpoints[i])||Double.isInfinite(xpoints[i])||Double.isNaN(re_points[i])||Double.isInfinite(re_points[i])||Double.isNaN(im_points[i])||Double.isInfinite(im_points[i])) { if((index==1)||(i==index-1)) { // we only have one point and it is a bad point! index--; break; // exit the loop } System.arraycopy(xpoints, i+1, xpoints, i, index-i-1); System.arraycopy(re_points, i+1, re_points, i, index-i-1); System.arraycopy(im_points, i+1, im_points, i, index-i-1); index--; } } } /** * Sets the array size. * @param newCapacity */ private void setCapacity(int newCapacity) { double[] tempx = xpoints; xpoints = new double[newCapacity]; System.arraycopy(tempx, 0, xpoints, 0, tempx.length); double[] tempre = re_points; re_points = new double[newCapacity]; System.arraycopy(tempre, 0, re_points, 0, tempre.length); double[] tempim = im_points; im_points = new double[newCapacity]; System.arraycopy(tempim, 0, im_points, 0, tempim.length); double[] tempamp = amp_points; amp_points = new double[newCapacity]; System.arraycopy(tempamp, 0, amp_points, 0, tempamp.length); } /** * Draw this Dataset in the drawing panel. * @param drawingPanel * @param g */ public void draw(DrawingPanel drawingPanel, Graphics g) { if(!visible) { return; } Graphics2D g2 = (Graphics2D) g; switch(markerShape) { case AMP_CURVE : drawLinePlot(drawingPanel, g2); break; case RE_IM_CURVE : drawReImPlot(drawingPanel, g2); break; case PHASE_CURVE : drawPhaseCurve(drawingPanel, g2); break; case PHASE_BAR : drawPhaseBars(drawingPanel, g2); break; case PHASE_POST : drawPhasePosts(drawingPanel, g2); break; } } /** * Clear all data from this Dataset. */ public void clear() { index = 0; xpoints = new double[initialSize]; re_points = new double[initialSize]; im_points = new double[initialSize]; amp_points = new double[initialSize]; ampPath.reset(); reTrail.clear(); imTrail.clear(); resetXYMinMax(); } /** * Create a string representation of the data. * @return the data */ public String toString() { if(index==0) { return "Dataset empty."; //$NON-NLS-1$ } String s = xpoints[0]+"\t"+re_points[0]+"\t"+im_points[0]+"\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ StringBuffer b = new StringBuffer(index*s.length()); for(int i = 0; i<index; i++) { b.append(xpoints[i]); b.append('\t'); b.append(re_points[i]); b.append('\t'); b.append(im_points[i]); b.append('\n'); // s += xpoints[i] + "\t" + ypoints[i] + "\n"; } return b.toString(); } /** * Gets the number of columns for rendering in a JTable. * @return the count */ public int getColumnCount() { return 3; } /** * Gets the number of rows for rendering in a JTable. * @return the count */ public int getRowCount() { return(index+stride-1)/stride; } /** * Gets the name of the colummn for rendering in a JTable * @param columnIndex * @return the name */ public String getColumnName(int columnIndex) { switch(columnIndex) { case 0 : return xColumnName; case 1 : return reColumnName; case 2 : return imColumnName; } return ""; //$NON-NLS-1$ } /** * Gets an x or y value for rendering in a JTable. * @param rowIndex * @param columnIndex * @return the datum */ public Object getValueAt(int rowIndex, int columnIndex) { rowIndex = rowIndex*stride; switch(columnIndex) { case 0 : return new Double(xpoints[rowIndex]); case 1 : return new Double(re_points[rowIndex]); case 2 : return new Double(im_points[rowIndex]); } return new Double(0); } /** * Gets the type of object for JTable entry. * @param columnIndex * @return the class */ public Class<?> getColumnClass(int columnIndex) { return Double.class; } /** * Reset the minimum and maximum values. */ private void resetXYMinMax() { // changed by W. Christian xmin = Double.MAX_VALUE; xmax = -Double.MAX_VALUE; remax = -Double.MAX_VALUE; remin = Double.MAX_VALUE; immax = -Double.MAX_VALUE; immin = Double.MAX_VALUE; ampmax = -Double.MAX_VALUE; ampmin = Double.MAX_VALUE; for(int i = 0; i<index; i++) { xmin = Math.min(xpoints[i], xmin); xmax = Math.max(xpoints[i], xmax); remax = Math.max(re_points[i], remax); remin = Math.min(re_points[i], remin); immax = Math.max(im_points[i], immax); immin = Math.min(im_points[i], immin); ampmax = Math.max(amp_points[i], ampmax); ampmin = Math.min(amp_points[i], ampmin); } } /** * Perform an insertion sort of the data set. * * Since data will be partially sorted this should be fast. * Added by W. Christian. */ protected void insertionSort() { boolean dataChanged = false; if(index<2) { return; // need at least two points to sort. } for(int i = 1; i<index; i++) { if(xpoints[i]<xpoints[i-1]) { // is the i-th datum smaller? dataChanged = true; moveDatum(i); } } if(dataChanged) { recalculatePath(); } } /** * Recalcualte the general path. */ protected void recalculatePath() { ampPath.reset(); if(index<1) { return; } float amp = (float) Math.sqrt(re_points[0]*re_points[0]+im_points[0]*im_points[0]); ampPath.moveTo((float) xpoints[0], amp); for(int i = 1; i<index; i++) { amp = (float) Math.sqrt(re_points[i]*re_points[i]+im_points[i]*im_points[i]); ampPath.lineTo((float) xpoints[i], amp); } } /** * Move an out-of-place datum into its correct position. * @param loc the datum */ protected void moveDatum(int loc) { if(loc<1) { return; // zero-th point cannot be out-of-place } double x = xpoints[loc]; // save the old values double re = re_points[loc]; double im = im_points[loc]; for(int i = 0; i<index; i++) { if(xpoints[i]>x) { // find the insertion point System.arraycopy(xpoints, i, xpoints, i+1, loc-i); xpoints[i] = x; System.arraycopy(re_points, i, re_points, i+1, loc-i); re_points[i] = re; System.arraycopy(im_points, i, im_points, i+1, loc-i); im_points[i] = im; return; } } } /** * Draw the lines connecting the data points. * @param drawingPanel * @param g2 */ protected void drawLinePlot(DrawingPanel drawingPanel, Graphics2D g2) { AffineTransform at = (AffineTransform) (drawingPanel.getPixelTransform().clone()); Shape s = ampPath.createTransformedShape(at); g2.setColor(lineColor); g2.draw(s); if(showPhase) { at.concatenate(flip); s = ampPath.createTransformedShape(at); g2.draw(s); } } /** * Draw the lines connecting the data points. * @param drawingPanel * @param g2 */ protected void drawReImPlot(DrawingPanel drawingPanel, Graphics2D g2) { reTrail.draw(drawingPanel, g2); imTrail.draw(drawingPanel, g2); } /** * Draw the phase as color. * @param drawingPanel * @param g2 */ protected void drawPhaseCurve(DrawingPanel drawingPanel, Graphics2D g2) { double[] xpoints = this.xpoints; double[] re_points = this.re_points; double[] im_points = this.im_points; int index = this.index; if(index<1) { return; } if((xpoints.length<index)||(xpoints.length!=re_points.length)||(xpoints.length!=im_points.length)) { return; } int yorigin = drawingPanel.yToPix(0); int[] xpix = new int[4]; int[] ypix = new int[4]; xpix[2] = drawingPanel.xToPix(xpoints[0]); double oldY = Math.sqrt(re_points[0]*re_points[0]+im_points[0]*im_points[0]); ypix[3] = drawingPanel.yToPix(-oldY); ypix[2] = drawingPanel.yToPix(oldY); double oldRe = re_points[0]; double oldIm = im_points[0]; for(int i = 0; i<index; i++) { double re = re_points[i]; double im = im_points[i]; double y = Math.sqrt(re*re+im*im); if(y>0) { g2.setColor(DisplayColors.phaseToColor(Math.atan2((im+oldIm)/2, (oldRe+re)/2))); } xpix[0] = drawingPanel.xToPix(xpoints[i]); if(centered) { ypix[0] = drawingPanel.yToPix(-y/2); ypix[1] = drawingPanel.yToPix(y/2); } else { ypix[0] = yorigin; ypix[1] = drawingPanel.yToPix(y); } // g2.drawLine(xnew,drawingPanel.yToPix(-y),xnew,drawingPanel.yToPix(y)); xpix[1] = xpix[0]; xpix[3] = xpix[2]; g2.fillPolygon(xpix, ypix, 4); xpix[2] = xpix[0]; ypix[3] = ypix[0]; ypix[2] = ypix[1]; oldIm = im; oldRe = re; oldY = y; } } /** * Draw the phase as a colored bar. * @param drawingPanel * @param g2 */ protected void drawPhaseBars(DrawingPanel drawingPanel, Graphics2D g2) { if(index<1) { return; } double[] xpoints = this.xpoints; double[] re_points = this.re_points; double[] im_points = this.im_points; if((xpoints.length<index)||(xpoints.length!=re_points.length)||(xpoints.length!=im_points.length)) { return; } int barWidth = (int) (0.5+(drawingPanel.xToPix(xmax)-drawingPanel.xToPix(xmin))/(2.0*(index-1))); barWidth = Math.min(markerSize, barWidth); int yorigin = drawingPanel.yToPix(0); for(int i = 0; i<index; i++) { double re = re_points[i]; double im = im_points[i]; double y = Math.sqrt(re*re+im*im); g2.setColor(DisplayColors.phaseToColor(Math.atan2(im, re))); int xpix = drawingPanel.xToPix(xpoints[i]); int height = Math.abs(yorigin-drawingPanel.yToPix(y)); if(centered) { g2.fillRect(xpix-barWidth, yorigin-height/2, 2*barWidth+1, height); } else { g2.fillRect(xpix-barWidth, yorigin-height, 2*barWidth+1, height); } } } /** * Draw the phase as a colored post. * @param drawingPanel * @param g2 */ protected void drawPhasePosts(DrawingPanel drawingPanel, Graphics2D g2) { if(index<1) { return; } double[] xpoints = this.xpoints; double[] re_points = this.re_points; double[] im_points = this.im_points; if((xpoints.length<index)||(xpoints.length!=re_points.length)||(xpoints.length!=im_points.length)) { return; } int postWidth = (int) (0.5+(drawingPanel.xToPix(xmax)-drawingPanel.xToPix(xmin))/(2.0*(index-1))); postWidth = Math.min(markerSize, postWidth); for(int i = 0; i<index; i++) { double re = re_points[i]; double im = im_points[i]; double y = Math.sqrt(re*re+im*im); drawPost(drawingPanel, g2, xpoints[i], y, postWidth, DisplayColors.phaseToColor(Math.atan2(im, re))); } } private void drawPost(DrawingPanel drawingPanel, Graphics2D g2, double x, double y, int postWidth, Color fillColor) { Color edgeColor = Color.BLACK; int xp = drawingPanel.xToPix(x); int yp = drawingPanel.yToPix(y); int size = postWidth*2+1; int bottom = Math.min(drawingPanel.yToPix(0), drawingPanel.yToPix(drawingPanel.getYMin())); Shape shape = new Rectangle2D.Double(xp-postWidth, yp-postWidth, size, size); g2.setColor(edgeColor); g2.drawLine(xp, yp, xp, bottom); g2.setColor(fillColor); g2.fill(shape); g2.setColor(edgeColor); g2.draw(shape); } /** * Returns the XML.ObjectLoader for this class. * * @return the object loader */ public static XML.ObjectLoader getLoader() { return new Loader(); } /** * Sets a name that can be used to identify the dataset. * * @param name String */ public void setName(String name) { this.name = name; } /** * Gets the name. * * @return String */ public String getName() { return name; } /** * The column names to be used in the data display tool * @return */ public String[] getColumnNames() { return new String[] {"Re", "Im"}; //$NON-NLS-1$ //$NON-NLS-2$ } public double[][] getData2D() { double[][] data = new double[3][index]; data[0] = getXPoints(); data[1] = getRePoints(); data[2] = getImPoints(); return data; } public double[][][] getData3D() { return null; } public ArrayList<Dataset> getDatasets() { if((reDataset==null)||(imDataset==null)) { reDataset = new Dataset(Color.RED, Color.RED, true); imDataset = new Dataset(Color.BLUE, Color.BLUE, true); } reDataset.clear(); imDataset.clear(); reDataset.setXYColumnNames(xColumnName, reColumnName, "Re("+name+")"); //$NON-NLS-1$ //$NON-NLS-2$ imDataset.setXYColumnNames(xColumnName, imColumnName, "Im("+name+")"); //$NON-NLS-1$ //$NON-NLS-2$ reDataset.append(getXPoints(), getRePoints()); imDataset.append(getXPoints(), getImPoints()); ArrayList<Dataset> list = new ArrayList<Dataset>(); list.add(reDataset); list.add(imDataset); return list; } /** * Some elements (a Group, for instance) do not contain data, but a list of subelements which do. * This method is used by Data displaying tools to create as many pages as needed. * @return A list of DataInformation elements, null if the element itself is a DataInformation */ public java.util.List<Data> getDataList() { if((reDataset==null)||(imDataset==null)) { reDataset = new Dataset(Color.RED, Color.RED, true); imDataset = new Dataset(Color.BLUE, Color.BLUE, true); } reDataset.clear(); imDataset.clear(); reDataset.setXYColumnNames(xColumnName, reColumnName, "Re("+name+")"); //$NON-NLS-1$ //$NON-NLS-2$ imDataset.setXYColumnNames(xColumnName, imColumnName, "Im("+name+")"); //$NON-NLS-1$ //$NON-NLS-2$ reDataset.append(getXPoints(), getRePoints()); imDataset.append(getXPoints(), getImPoints()); ArrayList<Data> list = new ArrayList<Data>(); list.add(reDataset); list.add(imDataset); return list; } /** * A class to save and load Dataset data in an XMLControl. */ private static class Loader extends XMLLoader { public void saveObject(XMLControl control, Object obj) { ComplexDataset data = (ComplexDataset) obj; control.setValue("points", data.getPoints()); //$NON-NLS-1$ // control.setValue("x_points", data.getXPoints()); // control.setValue("y_points", data.getYPoints()); control.setValue("marker_shape", data.getMarkerShape()); //$NON-NLS-1$ control.setValue("marker_size", data.getMarkerSize()); //$NON-NLS-1$ control.setValue("sorted", data.isSorted()); //$NON-NLS-1$ control.setValue("connected", data.isConnected()); //$NON-NLS-1$ control.setValue("name", data.name); //$NON-NLS-1$ control.setValue("x_name", data.xColumnName); //$NON-NLS-1$ control.setValue("re_name", data.reColumnName); //$NON-NLS-1$ control.setValue("im_name", data.imColumnName); //$NON-NLS-1$ control.setValue("line_color", data.lineColor); //$NON-NLS-1$ control.setValue("index", data.index); //$NON-NLS-1$ } public Object createObject(XMLControl control) { return new ComplexDataset(); } public Object loadObject(XMLControl control, Object obj) { ComplexDataset data = (ComplexDataset) obj; double[][] points = (double[][]) control.getObject("points"); //$NON-NLS-1$ if((points!=null)&&(points[0]!=null)) { data.clear(); for(int i = 0; i<points.length; i++) { data.append(points[i][0], points[i][1], points[i][2]); } } // for backward compatibility double[] xPoints = (double[]) control.getObject("x_points"); //$NON-NLS-1$ double[] yPoints = (double[]) control.getObject("y_points"); //$NON-NLS-1$ if((xPoints!=null)&&(yPoints!=null)) { data.clear(); data.append(xPoints, yPoints); } if(control.getPropertyNames().contains("marker_shape")) { //$NON-NLS-1$ data.setMarkerShape(control.getInt("marker_shape")); //$NON-NLS-1$ } if(control.getPropertyNames().contains("marker_size")) { //$NON-NLS-1$ data.setMarkerSize(control.getInt("marker_size")); //$NON-NLS-1$ } data.setSorted(control.getBoolean("sorted")); //$NON-NLS-1$ data.setConnected(control.getBoolean("connected")); //$NON-NLS-1$ data.name = control.getString("name"); //$NON-NLS-1$ data.xColumnName = control.getString("x_name"); //$NON-NLS-1$ data.reColumnName = control.getString("re_name"); //$NON-NLS-1$ data.imColumnName = control.getString("im_name"); //$NON-NLS-1$ Color color = (Color) control.getObject("line_color"); //$NON-NLS-1$ if(color!=null) { data.lineColor = color; } data.index = control.getInt("index"); //$NON-NLS-1$ return obj; } } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */