/*
* 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.display2d;
import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLLoader;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.InteractivePanel;
import org.opensourcephysics.display.axes.XAxis;
import org.opensourcephysics.display.axes.XYAxis;
public class ColorMapper {
// color palette types
private static final int CUSTOM = -1;
public static final int SPECTRUM = 0;
public static final int GRAYSCALE = 1;
public static final int DUALSHADE = 2;
public static final int RED = 3;
public static final int GREEN = 4;
public static final int BLUE = 5;
public static final int BLACK = 6;
public static final int WIREFRAME = 7; // special SurfacePlotter palette
public static final int NORENDER = 8; // special SurfacePlotter palette
public static final int REDBLUE_SHADE = 9; // special SurfacePlotter palette
private Color[] colors;
private double floor, ceil;
private Color floorColor = Color.darkGray;
private Color ceilColor = Color.lightGray;
private int numColors;
private int paletteType;
private JFrame legendFrame;
protected ZExpansion zMap = null;
/**
* Constructor ColorMapper
* @param _numColors
* @param _floor
* @param _ceil
* @param palette
*/
public ColorMapper(int _numColors, double _floor, double _ceil, int palette) {
floor = _floor;
ceil = _ceil;
numColors = _numColors;
setPaletteType(palette); // default colors
}
public void updateLegend(ZExpansion zMap) {
if((legendFrame!=null)&&legendFrame.isVisible()&&legendFrame.isDisplayable()) {
if(zMap==null) {
zMap = this.zMap; //use the local map if the parameter is null
}
showLegend(zMap);
}
}
public JFrame getLegendFrame() {
return legendFrame;
} // added by Paco
/**
* Shows the color legend.
*/
public JFrame showLegend() {
if(zMap!=null) {
return showLegend(zMap);
}
InteractivePanel dp = new InteractivePanel();
dp.setPreferredSize(new java.awt.Dimension(300, 66));
dp.setPreferredGutters(0, 0, 0, 35);
dp.setClipAtGutter(false);
if((legendFrame==null)||!legendFrame.isDisplayable()) {
legendFrame = new JFrame(DisplayRes.getString("GUIUtils.Legend")); //$NON-NLS-1$
}
legendFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
legendFrame.setResizable(false);
legendFrame.setContentPane(dp);
GridPointData pointdata = new GridPointData(numColors+2, 1, 1);
double[][][] data = pointdata.getData();
double delta = (ceil-floor)/(numColors);
double cval = floor-delta/2;
for(int i = 0, n = data.length; i<n; i++) {
data[i][0][2] = cval;
cval += delta;
}
pointdata.setScale(floor-delta, ceil+delta, 0, 1);
GridPlot cb = new GridPlot(pointdata);
cb.setShowGridLines(false);
cb.setAutoscaleZ(false, floor, ceil);
cb.setColorPalette(colors);
cb.update();
dp.addDrawable(cb);
XAxis xaxis = new XAxis(""); //$NON-NLS-1$
xaxis.setLocationType(XYAxis.DRAW_AT_LOCATION);
xaxis.setLocation(-0.5);
xaxis.setEnabled(true);
dp.addDrawable(xaxis);
legendFrame.pack();
legendFrame.setVisible(true);
return legendFrame;
}
/**
* Shows the color legend.
*/
JFrame showLegend(ZExpansion zMap) {
if(zMap==null) {
return showLegend();
}
InteractivePanel dp = new InteractivePanel();
dp.setPreferredSize(new java.awt.Dimension(300, 66));
dp.setPreferredGutters(0, 0, 0, 35);
dp.setClipAtGutter(false);
if((legendFrame==null)||!legendFrame.isDisplayable()) {
legendFrame = new JFrame(DisplayRes.getString("GUIUtils.Legend")); //$NON-NLS-1$
}
legendFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
legendFrame.setResizable(true);
legendFrame.setContentPane(dp);
int numColors = 256;
if(paletteType==CUSTOM) {
numColors = colors.length;
}
GridPointData pointdata = new GridPointData(numColors+2, 1, 1);
double[][][] data = pointdata.getData();
double delta = (ceil-floor)/(numColors);
double cval = floor-delta/2;
for(int i = 0, n = data.length; i<n; i++) {
data[i][0][2] = zMap.evaluate(cval);
cval += delta;
}
pointdata.setScale(floor-delta, ceil+delta, 0, 1);
GridPlot cb = new GridPlot(pointdata);
cb.setShowGridLines(false);
cb.setAutoscaleZ(false, floor, ceil);
if(paletteType==CUSTOM) {
cb.setColorPalette(colors);
} else {
cb.setPaletteType(paletteType);
}
cb.update();
dp.addDrawable(cb);
XAxis xaxis = new XAxis(""); //$NON-NLS-1$
xaxis.setLocationType(XYAxis.DRAW_AT_LOCATION);
xaxis.setLocation(-0.5);
xaxis.setEnabled(true);
dp.addDrawable(xaxis);
legendFrame.pack();
legendFrame.setVisible(true);
return legendFrame;
}
/**
* Sets the scale.
* @param _floor
* @param _ceil
*/
public void setScale(double _floor, double _ceil) {
floor = _floor;
ceil = _ceil;
if(zMap!=null) {
zMap.setMinMax(floor, ceil);
}
}
/**
* Converts a double to color components.
*
* @param value double
* @param rgb byte[]
* @return byte[]
*/
public byte[] doubleToComponents(double value, byte[] rgb) {
if(zMap!=null) {
value = zMap.evaluate(value);
}
Color color = doubleToColor(value);
rgb[0] = (byte) color.getRed();
rgb[1] = (byte) color.getGreen();
rgb[2] = (byte) color.getBlue();
return rgb;
}
/**
* Converts a double to a color.
* @param value
* @return the color
*/
public Color doubleToColor(double value) { // Changed by Paco to use doubleToIndex
int index = doubleToIndex(value);
if(index<0) return floorColor;
if(index>=colors.length) return ceilColor;
return colors[index];
}
/**
* Converts a double to an index in the color array.
* @param value
* @return the index in the array with the following exceptions:
* <ul>
* <li>-1 if floor color</li>
* <li>colors.length if ceil color</li>
* </ul>
*/
public int doubleToIndex(double value) { // Added by Paco
if(zMap!=null) {
value = zMap.evaluate(value);
}
if((float) floor-(float) value>Float.MIN_VALUE) {
return -1;
} else if((float) value-(float) ceil>Float.MIN_VALUE) {
return colors.length;
}
int index = (int) (colors.length*(value-floor)/(ceil-floor));
index = Math.max(0, index);
return Math.min(index, colors.length-1);
}
/**
* Returns the color for an index
*/
public Color indexToColor(int index) { // Added by Paco
if (index<0) return floorColor;
if (index>=colors.length) return ceilColor;
return colors[index];
}
/**
* Returns the thresholds for color change. One more than colors, includes ceil and floor
*/
public double[] getColorThresholds() { // Added by Paco
double[] thresholds = new double[colors.length+1];
double delta = (ceil-floor)/colors.length;
for (int i=0,n=colors.length; i<n; i++) thresholds[i] = floor + i*delta;
thresholds[colors.length] = ceil;
return thresholds;
}
/**
* Sets map for z values.
*
* @param map ZExpansion
*/
public void setZMap(ZExpansion map) {
zMap = map;
if(zMap!=null) {
zMap.setMinMax(floor, ceil);
}
}
/**
* Gets the floor.
* @return
*/
public double getFloor() {
return floor;
}
/**
* Gets the floor color;
* @return
*/
public Color getFloorColor() {
return floorColor;
}
/**
* Gets the ceiling color.
* @return
*/
public double getCeil() {
return ceil;
}
/**
* Gets the ceiling color.
* @return
*/
public Color getCeilColor() {
return ceilColor;
}
/**
* Gets the number of colors between the floor and ceiling values.
* @return
*/
public int getNumColors() {
return numColors;
}
/**
* Sets the floor and ceiling colors.
*
* @param _floorColor
* @param _ceilColor
*/
public void setFloorCeilColor(Color _floorColor, Color _ceilColor) {
floorColor = _floorColor;
ceilColor = _ceilColor;
}
/**
* Returns the color palette.
* @return mode
*/
public int getPaletteType() {
return paletteType;
}
/**
* Sets the color palette.
* @param _colors
*/
public void setColorPalette(Color[] _colors) {
floorColor = Color.darkGray;
ceilColor = Color.lightGray;
colors = _colors;
numColors = colors.length;
paletteType = CUSTOM;
}
/**
* Sets the number of colors
* @param _numColors
*/
public void setNumberOfColors(int _numColors) {
if(_numColors==numColors) {
return;
}
numColors = _numColors;
if(paletteType==CUSTOM) {
Color newColors[] = new Color[numColors];
for(int i = 0, n = Math.min(colors.length, numColors); i<n; i++) {
newColors[i] = colors[i];
}
for(int i = colors.length; i<numColors; i++) {
newColors[i] = colors[colors.length-1];
}
colors = newColors;
} else {
setPaletteType(paletteType);
}
}
/**
* Sets the color palette.
* @param _paletteType
*/
public void setPaletteType(int _paletteType) {
paletteType = _paletteType;
floorColor = Color.darkGray;
ceilColor = Color.lightGray;
if((paletteType==GRAYSCALE)||(paletteType==BLACK)) {
floorColor = new Color(64, 64, 128);
ceilColor = new Color(255, 191, 191);
}
colors = getColorPalette(numColors, paletteType);
numColors = Math.max(2, numColors); // need at least 2 colors
}
/**
* Gets a array of colors for use in data visualization.
*
* Colors are similar to the colors returned by a color mapper instance.
* @param numColors
* @param paletteType
* @return
*/
static public Color[] getColorPalette(int numColors, int paletteType) {
if(numColors<2) {
numColors = 2;
}
Color colors[] = new Color[numColors];
for(int i = 0; i<numColors; i++) {
float level = (float) i/(numColors-1)*0.8f;
int r = 0, b = 0;
switch(paletteType) {
case ColorMapper.REDBLUE_SHADE :
r = (Math.max(0, -numColors-1+i*2)*255)/(numColors-1);
b = (Math.max(0, numColors-1-i*2)*255)/(numColors-1);
colors[i] = new Color(r, 0, b);
break;
case ColorMapper.SPECTRUM :
level = 0.8f-level;
colors[i] = Color.getHSBColor(level, 1.0f, 1.0f);
break;
case ColorMapper.GRAYSCALE :
case ColorMapper.BLACK :
colors[i] = new Color(i*255/(numColors-1), i*255/(numColors-1), i*255/(numColors-1));
break;
case ColorMapper.RED :
colors[i] = new Color(i*255/(numColors-1), 0, 0);
break;
case ColorMapper.GREEN :
colors[i] = new Color(0, i*255/(numColors-1), 0);
break;
case ColorMapper.BLUE :
colors[i] = new Color(0, 0, i*255/(numColors-1));
break;
case ColorMapper.DUALSHADE :
default :
level = (float) i/(numColors-1);
colors[i] = Color.getHSBColor(0.8f*(1-level), 1.0f, 0.2f+1.6f*Math.abs(0.5f-level));
break;
}
}
return colors;
}
/**
* Gets a loader that allows a Circle to be represented as XML data.
* Objects without XML loaders cannot be saved and retrieved from an XML file.
*
* @return ObjectLoader
*/
public static XML.ObjectLoader getLoader() {
return new ColorMapperLoader();
}
/**
* A class to save and load Circle objects in an XMLControl.
*/
private static class ColorMapperLoader extends XMLLoader {
/**
* Saves the ColorMapper's data in the xml control.
* @param control XMLControl
* @param obj Object
*/
public void saveObject(XMLControl control, Object obj) {
ColorMapper mapper = (ColorMapper) obj;
control.setValue("palette type", mapper.paletteType); //$NON-NLS-1$
control.setValue("number of colors", mapper.numColors); //$NON-NLS-1$
control.setValue("floor", mapper.floor); //$NON-NLS-1$
control.setValue("ceiling", mapper.ceil); //$NON-NLS-1$
control.setValue("floor color", mapper.floorColor); //$NON-NLS-1$
control.setValue("ceiling color", mapper.ceilColor); //$NON-NLS-1$
if(mapper.paletteType==CUSTOM) {
control.setValue("colors", mapper.colors); //$NON-NLS-1$
}
}
/**
* Creates a ColorMapper.
* @param control XMLControl
* @return Object
*/
public Object createObject(XMLControl control) {
return new ColorMapper(100, -1, 1, ColorMapper.SPECTRUM);
}
/**
* Loads data from the xml control into the ColorMapper object.
* @param control XMLControl
* @param obj Object
* @return Object
*/
public Object loadObject(XMLControl control, Object obj) {
ColorMapper mapper = (ColorMapper) obj;
int paletteType = control.getInt("palette type"); //$NON-NLS-1$
int numColors = control.getInt("number of colors"); //$NON-NLS-1$
double floor = control.getDouble("floor"); //$NON-NLS-1$
double ceil = control.getDouble("ceiling"); //$NON-NLS-1$
if(paletteType==CUSTOM) {
Color[] colors = (Color[]) control.getObject("colors"); //$NON-NLS-1$
mapper.setColorPalette(colors);
} else {
mapper.setPaletteType(paletteType);
mapper.setNumberOfColors(numColors);
}
mapper.setScale(floor, ceil);
Color floorColor = (Color) control.getObject("floor color"); //$NON-NLS-1$
Color ceilColor = (Color) control.getObject("ceiling color"); //$NON-NLS-1$
mapper.setFloorCeilColor(floorColor, ceilColor);
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
*/