Version 7177

This commit is contained in:
Arnaud Roques 2011-09-07 22:41:58 +02:00
parent df0e7faa11
commit 7cee7b4601
315 changed files with 25794 additions and 940 deletions

View File

@ -0,0 +1,373 @@
package com.ctreber.acearth;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import com.ctreber.acearth.gui.CanvasACearth;
import com.ctreber.acearth.plugins.Plugin;
import com.ctreber.acearth.plugins.markers.Marker;
import com.ctreber.acearth.plugins.markers.PluginMarkers;
import com.ctreber.acearth.projection.Projection;
import com.ctreber.acearth.projection.ProjectionCyl;
import com.ctreber.acearth.projection.ProjectionMerc;
import com.ctreber.acearth.projection.ProjectionOrtho;
import com.ctreber.acearth.renderer.Renderer;
import com.ctreber.acearth.renderer.RowTypeRendererScanBit;
import com.ctreber.acearth.renderer.RowTypeRendererScanDot;
import com.ctreber.acearth.scanbit.BitGeneratorMap;
import com.ctreber.acearth.scanbit.BitGeneratorMapDefault;
import com.ctreber.acearth.scanbit.BitGeneratorMapOrtho;
import com.ctreber.acearth.scandot.DotGeneratorLines;
import com.ctreber.acearth.scandot.DotGeneratorStars;
import com.ctreber.acearth.scandot.ScanDot;
import com.ctreber.acearth.scandot.ScanDotGenerator;
import com.ctreber.acearth.shader.Shader;
import com.ctreber.acearth.shader.ShaderDefault;
import com.ctreber.acearth.shader.ShaderFlat;
import com.ctreber.acearth.shader.ShaderOrtho;
import com.ctreber.acearth.util.Coordinate;
import com.ctreber.acearth.util.SunPositionCalculator;
import com.ctreber.acearth.util.Toolkit;
import com.ctreber.aclib.sort.CTSort;
import com.ctreber.aclib.sort.QuickSort;
/**
* <h1>AC.earth - XEarth for Java
* <h1>
*
* <p>
* The original XEarth was written by Kirk Johnson in July 1993 - thank you for
* writing this great little program and making it available for free!
*
* <p>
* I wanted to extend the program, but not in C. So I created this Java version,
* and found the process quite <strike>painfull</strike> interesting. The
* biggest effort went into resolving references between C files and
* eliminatiing pointers.
*
* <h1>License</h1>
*
* <p>
* AC.earth Copyright (c) 2002 Christian Treber, ct@ctreber.com
*
* <p>
* AC.earth is based on XEarth by Kirk Johnson
*
* <p>
* To comply with the XEarth license I include the following text:
*
* <pre>
* XEarth Copyright (C) 1989, 1990, 1993-1995, 1999 Kirk Lauritz Johnson
* Parts of the source code are:
* Copyright (C) 1989, 1990, 1991 by Jim Frost
* Copyright (C) 1992 by Jamie Zawinski &lt;jwz@lucid.com&gt;
* Permission to use, copy, modify and freely distribute xearth for
* non-commercial and not-for-profit purposes is hereby granted
* without fee, provided that both the above copyright notice and this
* permission notice appear in all copies and in supporting
* documentation.
* [Section refering to GIF omitted because it doesn't apply to this version]
* The author makes no representations about the suitability of this
* software for any purpose. It is provided &quot;as is&quot; without express or
* implied warranty.
* THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT
* OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
* </pre>
*
* <p>
* The license for this program (AC.earth) is the same as the quoted license
* above, with one change: The "copyright notice and permission notice" shall
* include the entire text of this section.
*
* todo Phase 2: Make grid value stuff more meaningful ("every n degrees") todo
* Phase 2: Enter fixed time as data and time, not seconds since epoch todo
* Phase 2: Compact map data into binary file
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*/
public class ACearth {
public static final String VERSION = "1.1";
public static final String BUILD = "22.11.2002 004";
// private static long fsStartTime = 0;
private ConfigurationACearth fConf = new ConfigurationACearth();
private long fCurrentTime;
private CanvasACearth fCanvas;
private Coordinate fViewPos;
private double fViewRotation;
private List fPlugins;
/**
* <p>
* Well, the main class.
* @param markers
*/
public ACearth(List<Marker> markers) {
// fsStartTime = System.currentTimeMillis();
fPlugins = new ArrayList();
fPlugins.add(new PluginMarkers(markers));
}
public void exportPng(OutputStream os) throws IOException {
fCanvas = new CanvasACearth(this, fConf.getInt("imageWidth"), fConf.getInt("imageHeight"));
update();
fCanvas.saveToImage(os);
}
public void update() throws IOException {
Projection lProjection = null;
Shader lShader = null;
BitGeneratorMap lScanner = null;
if (fConf.is("projection", "Cylindrical")) {
lProjection = new ProjectionCyl();
lScanner = new BitGeneratorMapDefault(lProjection);
lShader = new ShaderDefault();
}
if (fConf.is("projection", "Mercator")) {
lProjection = new ProjectionMerc();
lScanner = new BitGeneratorMapDefault(lProjection);
lShader = new ShaderDefault();
}
if (fConf.is("projection", "Orthographic")) {
lProjection = new ProjectionOrtho();
lScanner = new BitGeneratorMapOrtho(lProjection);
lShader = new ShaderOrtho();
}
computePositions();
lProjection.setImageWidth(fConf.getInt("imageWidth"));
lProjection.setImageHeight(fConf.getInt("imageHeight"));
lProjection.setShiftX(fConf.getInt("shiftX"));
lProjection.setShiftY(fConf.getInt("shiftY"));
lProjection.setViewMagnification(fConf.getDouble("viewMagnification"));
lProjection.setViewPos(fViewPos);
lProjection.setViewRotation(fViewRotation);
lScanner.setImageWidth(fConf.getInt("imageWidth"));
lScanner.setImageHeight(fConf.getInt("imageHeight"));
lScanner.setMapData(MapDataReader.readMapData());
// Process the map (produces ScanBit-s).
lScanner.generateScanBits();
// Process stars and lines (produces ScanDots-s).
List lScanDots = new ArrayList();
if (fConf.getBoolean("starsP")) {
ScanDotGenerator lGenerator = new DotGeneratorStars(fConf.getInt("imageWidth"),
fConf.getInt("imageHeight"), fConf.getDouble("starFrequency"), fConf.getInt("bigStars"), new Random(fCurrentTime));
lGenerator.generateScanDots();
lScanDots.addAll(lGenerator.getScanDots());
}
if (fConf.getBoolean("gridP")) {
ScanDotGenerator lGenerator = new DotGeneratorLines(lProjection, fConf.getInt("gridDivision"), fConf
.getInt("gridPixelDivision"));
lGenerator.generateScanDots();
lScanDots.addAll(lGenerator.getScanDots());
}
final CTSort lSort = new QuickSort();
ScanDot[] lScanDotsArray = (ScanDot[]) lScanDots.toArray(new ScanDot[0]);
lSort.sort(lScanDotsArray);
if (!fConf.getBoolean("shadeP")) {
lShader = new ShaderFlat();
}
lShader.setProjection(lProjection);
lShader.setSunPos(fConf.getSunPos());
lShader.setDaySideBrightness(fConf.getInt("daySideBrightness"));
lShader.setTerminatorDiscontinuity(fConf.getInt("terminatorDiscontinuity"));
lShader.setNightSideBrightness(fConf.getInt("nightSideBrightness"));
lShader.init();
Renderer lRenderer = new Renderer(fCanvas);
RowTypeRendererScanBit lRowRendererScanBit = new RowTypeRendererScanBit();
lRowRendererScanBit.setScanBits(lScanner.getScanBits());
lRenderer.addRowTypeRenderer(lRowRendererScanBit);
RowTypeRendererScanDot lRowRendererScanDot = new RowTypeRendererScanDot();
lRowRendererScanDot.setScanDots(lScanDotsArray);
lRenderer.addRowTypeRenderer(lRowRendererScanDot);
lRenderer.setShader(lShader);
lRenderer.render();
// Apply plugins
Iterator lIt = fPlugins.iterator();
while (lIt.hasNext()) {
Plugin lPlugin = (Plugin) lIt.next();
lPlugin.setProjection(lProjection);
lPlugin.setRenderTarget(fCanvas);
lPlugin.setParent(this);
lPlugin.render();
}
}
/**
* <p>
* This is repeated when time changes since this influences the position of
* Earth.
*/
private void computePositions() {
// Determine time for rendering
if (fConf.getInt("fixedTime") == 0) {
// No fixed time.
// final long lTimePassed = System.currentTimeMillis() - fsStartTime;
// fCurrentTime = fsStartTime + (long) (fConf.getDouble("timeWarpFactor") * lTimePassed);
fCurrentTime = System.currentTimeMillis();
} else {
// Fixed time.
fCurrentTime = fConf.getInt("fixedTime") * 1000L;
}
if (fConf.getBoolean("sunMovesP")) {
fConf.setSunPos(SunPositionCalculator.getSunPositionOnEarth(fCurrentTime));
}
// Determine viewing position
if (fConf.is("viewPositionType", "Fixed")) {
fViewPos = fConf.getViewPos();
} else if (fConf.is("viewPositionType", "Sun-relative")) {
fViewPos = getSunRelativePosition();
} else if (fConf.is("viewPositionType", "Orbit")) {
fViewPos = getOrbitPosition(fCurrentTime);
} else if (fConf.is("viewPositionType", "Random")) {
fViewPos = getRandomPosition();
} else if (fConf.is("viewPositionType", "Moon")) {
fViewPos = SunPositionCalculator.getMoonPositionOnEarth(fCurrentTime);
}
// for ViewRotGalactic, compute appropriate viewing rotation
if (fConf.is("viewRotationType", "Galactic")) {
fViewRotation = (Toolkit.degsToRads(fConf.getSunPos().getLat()
* Math.sin((fViewPos.getLong() - fConf.getSunPos().getLong()))));
} else {
fViewRotation = fConf.getDouble("viewRotation");
}
}
/**
* <p>
* Add sun position and position relative to sun, straighten out the result.
*
* @return Position relativ to sun position as defined by fSunPosRel.
*/
private Coordinate getSunRelativePosition() {
final Coordinate lPos = fConf.getSunPos();
lPos.add(fConf.getSunPosRel());
return lPos;
}
private Coordinate getOrbitPosition(long pTimeMillis) {
double x, y, z;
double a, c, s;
double t1, t2;
/* start at 0 N 0 E */
x = 0;
y = 0;
z = 1;
/*
* rotate in about y axis (from z towards x) according to the number of
* orbits we've completed
*/
a = (double) pTimeMillis / (fConf.getDouble("orbitPeriod") * 3600 * 1000) * 2 * Math.PI;
c = Math.cos(a);
s = Math.sin(a);
t1 = c * z - s * x;
t2 = s * z + c * x;
z = t1;
x = t2;
/*
* rotate about z axis (from x towards y) according to the inclination
* of the orbit
*/
a = Toolkit.degsToRads(fConf.getDouble("orbitInclination"));
c = Math.cos(a);
s = Math.sin(a);
t1 = c * x - s * y;
t2 = s * x + c * y;
x = t1;
y = t2;
/*
* rotate about y axis (from x towards z) according to the number of
* rotations the earth has made
*/
a = ((double) pTimeMillis / 86400000) * (2 * Math.PI);
c = Math.cos(a);
s = Math.sin(a);
t1 = c * x - s * z;
t2 = s * x + c * z;
x = t1;
z = t2;
return new Coordinate(Toolkit.radsToDegs(Math.asin(y)), Toolkit.radsToDegs(Math.atan2(x, z)));
}
/**
* <p>
* Pick a position (lat, lon) at random
*
* @return A random position.
*/
private static Coordinate getRandomPosition() {
/* select a vector at random */
final double[] pos = new double[3];
double mag = 0;
do {
for (int i = 0; i < 3; i++) {
pos[i] = ((Math.random() * 20000) * 1e-4) - 1;
mag += pos[i] * pos[i];
}
} while ((mag > 1.0) || (mag < 0.01));
/* normalize the vector */
mag = Math.sqrt(mag);
for (int i = 0; i < 3; i++) {
pos[i] /= mag;
}
/* convert to (lat, lon) */
final double s_lat = pos[1];
final double c_lat = Math.sqrt(1 - s_lat * s_lat);
final double s_lon = pos[0] / c_lat;
final double c_lon = pos[2] / c_lat;
return new Coordinate(Math.atan2(s_lat, c_lat) * (180 / Math.PI), Math.atan2(s_lon, c_lon) * (180 / Math.PI));
}
// public static long getStartTime() {
// return fsStartTime;
// }
public ConfigurationACearth getConf() {
return fConf;
}
}

View File

@ -0,0 +1,108 @@
package com.ctreber.acearth;
import java.util.HashMap;
import java.util.Map;
import com.ctreber.aclib.gui.MOBoolean;
import com.ctreber.aclib.gui.MODouble;
import com.ctreber.aclib.gui.MOEnum;
import com.ctreber.aclib.gui.MOInteger;
import com.ctreber.aclib.gui.MOString;
import com.ctreber.aclib.gui.MonitoredObject;
/**
* <p>
* </p>
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com (06.10.2002)
* </p>
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class Configuration {
private Map fValues = new HashMap();
/**
* <p>
* Item must be added before it can be set or get.
*
* @param pID
* Item name.
* @param pObject
* Item value container.
*/
public void add(String pID, MonitoredObject pObject) {
fValues.put(pID, pObject);
}
public void setString(String pID, String pValue) {
((MOString) fValues.get(pID)).set(pValue);
}
public void setBoolean(String pID, boolean pValue) {
((MOBoolean) fValues.get(pID)).set(pValue);
}
public void setInt(String pID, int pValue) {
((MOInteger) fValues.get(pID)).set(pValue);
}
public void setDouble(String pID, double pValue) {
((MODouble) fValues.get(pID)).set(pValue);
}
public void setEnum(String pID, Object pValue) {
((MOEnum) fValues.get(pID)).set(pValue);
}
public String getString(String pID) {
return ((MOString) fValues.get(pID)).get();
}
public boolean getBoolean(String pID) {
return ((MOBoolean) fValues.get(pID)).get();
}
public int getInt(String pID) {
return ((MOInteger) fValues.get(pID)).get();
}
public double getDouble(String pID) {
return ((MODouble) fValues.get(pID)).get();
}
public boolean is(String pID, Object pValue) {
return ((MOEnum) fValues.get(pID)).is(pValue);
}
public MOBoolean getMOBoolean(String pID) {
return (MOBoolean) getMO(pID);
}
public MOString getMOString(String pID) {
return (MOString) getMO(pID);
}
public MOEnum getMOEnum(String pID) {
return (MOEnum) getMO(pID);
}
public MOInteger getMOInteger(String pID) {
return (MOInteger) getMO(pID);
}
public MODouble getMODouble(String pID) {
return (MODouble) getMO(pID);
}
public MonitoredObject getMO(String pID) {
final MonitoredObject lMO = (MonitoredObject) fValues.get(pID);
if (lMO == null) {
throw new IllegalArgumentException("Unknown conf item '" + pID + "'");
}
return lMO;
}
}

View File

@ -0,0 +1,152 @@
package com.ctreber.acearth;
import com.ctreber.acearth.util.Coordinate;
import com.ctreber.aclib.gui.MOBoolean;
import com.ctreber.aclib.gui.MODouble;
import com.ctreber.aclib.gui.MOEnum;
import com.ctreber.aclib.gui.MOInteger;
import com.ctreber.aclib.gui.MOString;
import com.ctreber.aclib.gui.MonitoredObject;
/**
* <p>
* How to avoid writing all the accessors? Code generator that creates derived
* class from template class? Configuration items in data structure?
* </p>
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com (06.10.2002)
* </p>
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class ConfigurationACearth extends Configuration {
private static final int DEFAULT_DIMENSION = 512;
public ConfigurationACearth() {
final MOEnum lProjection = new MOEnum();
lProjection.addValidValue("Mercator");
lProjection.addValidValue("Orthographic");
lProjection.addValidValue("Cylindrical");
lProjection.set("Orthographic");
add("projection", (MonitoredObject) lProjection);
final MOEnum lPositionType = new MOEnum();
lPositionType.addValidValue("Fixed");
lPositionType.addValidValue("Sun-relative");
lPositionType.addValidValue("Orbit");
lPositionType.addValidValue("Random");
lPositionType.addValidValue("Moon");
lPositionType.set("Sun-relative");
add("viewPositionType", lPositionType);
final MOEnum lViewRotationType = new MOEnum();
lViewRotationType.addValidValue("North");
lViewRotationType.addValidValue("Galactic");
lViewRotationType.set("North");
add("viewRotationType", lViewRotationType);
final MOString lOutputMode = new MOString("gui");
add("outputMode", lOutputMode);
// Only relevant if view type is "Fixed"./
final MODouble lViewPosLat = new MODouble(0, -90, +90);
add("viewPosLat", lViewPosLat);
final MODouble lViewPosLong = new MODouble(0, -180, +180);
add("viewPosLong", lViewPosLong);
// Only relevant if view type is "Sun-relative".
final MODouble lSunPosRelLat = new MODouble(0, -90, +90);
add("sunPosRelLat", lSunPosRelLat);
final MODouble lSunPosRelLong = new MODouble(0, -180, +180);
add("sunPosRelLong", lSunPosRelLong);
final MOBoolean lSunMovesP = new MOBoolean(true);
add("sunMovesP", lSunMovesP);
// Only relevant if sun does not move.
final MODouble lSunPosLat = new MODouble(0, -90, +90);
add("sunPosLat", lSunPosLat);
final MODouble lSunPosLong = new MODouble(0, -180, +180);
add("sunPosLong", lSunPosLong);
final MODouble lTimeWarpFactor = new MODouble(1.0, 0, Double.MAX_VALUE);
add("timeWarpFactor", lTimeWarpFactor);
final MOInteger lFixedTime = new MOInteger(0, 0, Integer.MAX_VALUE);
add("fixedTime", lFixedTime);
final MOInteger lWaitTime = new MOInteger(300, 0, Integer.MAX_VALUE);
add("waitTime", lWaitTime);
final MODouble lOrbitPeriod = new MODouble(1, 0.0001, Double.MAX_VALUE);
add("orbitPeriod", lOrbitPeriod);
final MODouble lOrbitInclination = new MODouble(45.0, 0, 90);
add("orbitInclination", lOrbitInclination);
final MOBoolean lLabelP = new MOBoolean(false);
add("labelP", lLabelP);
final MOInteger lImageWidth = new MOInteger(DEFAULT_DIMENSION, 0, Integer.MAX_VALUE);
add("imageWidth", lImageWidth);
final MOInteger lImageHeight = new MOInteger(DEFAULT_DIMENSION, 0, Integer.MAX_VALUE);
add("imageHeight", lImageHeight);
final MOBoolean lStarsP = new MOBoolean(true);
add("starsP", lStarsP);
final MODouble lStarFrequency = new MODouble(0.002, 0, Double.MAX_VALUE);
add("starFrequency", lStarFrequency);
final MOInteger lBigStars = new MOInteger(0, 0, 100);
add("bigStars", lBigStars);
final MOBoolean lGridP = new MOBoolean(true);
add("gridP", lGridP);
final MOInteger lGridDivision = new MOInteger(6, 0, Integer.MAX_VALUE);
add("gridDivision", lGridDivision);
final MOInteger lGridPixelDevision = new MOInteger(15, 0, Integer.MAX_VALUE);
add("gridPixelDivision", lGridPixelDevision);
final MOInteger lShiftX = new MOInteger(0, 0, Integer.MAX_VALUE);
add("shiftX", lShiftX);
final MOInteger lShiftY = new MOInteger(0, 0, Integer.MAX_VALUE);
add("shiftY", lShiftY);
final MODouble lViewMagnification = new MODouble(1.0, 0, Double.MAX_VALUE);
add("viewMagnification", lViewMagnification);
final MOBoolean lShadeP = new MOBoolean(true);
add("shadeP", lShadeP);
final MOInteger lDaySideBrightness = new MOInteger(100, 0, 100);
add("daySideBrightness", lDaySideBrightness);
final MOInteger lNightSideBrightness = new MOInteger(5, 0, 100);
add("nightSideBrightness", lNightSideBrightness);
final MOInteger lTerminatorDiscontinuity = new MOInteger(1, 0, 100);
add("terminatorDiscontinuity", lTerminatorDiscontinuity);
final MODouble lViewRotation = new MODouble(0, 0, Double.MAX_VALUE);
add("viewRotation", lViewRotation);
}
public Coordinate getViewPos() {
return new Coordinate(getDouble("viewPosLat"), getDouble("viewPosLong"));
}
public void setViewPos(Coordinate pViewPos) {
setDouble("viewPosLat", pViewPos.getLat());
setDouble("viewPosLong", pViewPos.getLong());
}
public Coordinate getSunPos() {
return new Coordinate(getDouble("sunPosLat"), getDouble("sunPosLong"));
}
public void setSunPos(Coordinate pSunPos) {
setDouble("sunPosLat", pSunPos.getLat());
setDouble("sunPosLong", pSunPos.getLong());
}
public Coordinate getSunPosRel() {
return new Coordinate(getDouble("sunPosRelLat"), getDouble("sunPosRelLong"));
}
public void setSunPosRel(Coordinate pSunPosRel) {
setDouble("sunPosRelLat", pSunPosRel.getLat());
setDouble("sunPosRelLong", pSunPosRel.getLong());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,110 @@
package com.ctreber.acearth;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import com.ctreber.acearth.util.Point3D;
import com.ctreber.acearth.util.Polygon;
/**
* The map data file is a big array of short (16-bit) ints, as follows: - it is
* a sequence of closed curves - the first value in a curve is the number of
* points in the curve - the second value in a curve indicates land/water (1 or
* -1, respectively) - this is followed by an [x,y,z] triple that indicates a
* point on the unit sphere (each of x, y, and z has been scaled by 30000),
* where the x axis points "to the right" (towards 0 N 90 E), the y axis points
* "up" (towards the north pole), and the z axis points "out of the screen"
* (towards 0 N 0 E). this is the starting point of the curve. - this is
* followed by (one less than the number of points in the curve) triples
* [dx,dy,dz]; the [x,y,z] triple for each successive point in the curve is
* obtained by adding [dx,dy,dz] onto the previous [x,y,z] values. - the curves
* are [must be!] non-self-intersecting and traced in a counter-clockwise
* direction
*
* the curves are sampled at a (roughly) a 20 mile resolution.
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class MapDataReader {
/** Point value scale (devide value by this number). */
private static final double MAP_DATA_SCALE = 30000.0;
private static List fData;
private static List fPolygons;
private static int fIndex;
/**
* <p>
* Read map data.
*
* @param pFileName
* Map data file name.
* @return Array of map polygons.
* @throws IOException
*/
public static Polygon[] readMapData() throws IOException {
final List lines = new MapData().getLines();
fData = new ArrayList();
for (Iterator it = lines.iterator(); it.hasNext(); ) {
String lLine = (String) it.next();
if (lLine.indexOf("/*") != -1) {
// Filter out comments.
continue;
}
StringTokenizer lST = new StringTokenizer(lLine, ", ");
while (lST.hasMoreTokens()) {
String lToken = lST.nextToken();
final Integer lValue = new Integer(lToken);
fData.add(lValue);
}
}
fPolygons = new ArrayList();
fIndex = 0;
while (getValue(fIndex) != 0) {
processCurve();
}
return (Polygon[]) fPolygons.toArray(new Polygon[0]);
}
private static void processCurve() {
final int lNPoint = getValue(fIndex++);
final int lType = getValue(fIndex++);
final Point3D[] lPoints = new Point3D[lNPoint];
final Point3D lPoint3D = new Point3D(getValue(fIndex++) / MAP_DATA_SCALE, getValue(fIndex++) / MAP_DATA_SCALE,
getValue(fIndex++) / MAP_DATA_SCALE);
lPoints[0] = lPoint3D;
for (int i = 1; i < lNPoint; i++) {
lPoints[i] = new Point3D(lPoints[i - 1].getX() + getValue(fIndex++) / MAP_DATA_SCALE, lPoints[i - 1].getY()
+ getValue(fIndex++) / MAP_DATA_SCALE, lPoints[i - 1].getZ() + getValue(fIndex++) / MAP_DATA_SCALE);
}
final Polygon lPolygon = new Polygon(lType, lPoints);
fPolygons.add(lPolygon);
}
/**
* <p>
* Get value of raw data at specified point.
*
* @param pIndex
* Index of value.
* @return Value of raw data at specified point.
*/
private static int getValue(int pIndex) {
return ((Integer) fData.get(pIndex)).intValue();
}
}

View File

@ -0,0 +1,21 @@
package com.ctreber.acearth.gui;
import com.ctreber.acearth.ACearth;
/**
* <p>
* Adds some mouse magic to the normal PixelCanvas.
* </p>
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com (Nov 8, 2002)
* </p>
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class CanvasACearth extends PixelCanvas {
public CanvasACearth(ACearth pParent, int pWidth, int pHeight) {
super(pWidth, pHeight);
}
}

View File

@ -0,0 +1,72 @@
package com.ctreber.acearth.gui;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import com.ctreber.acearth.renderer.RenderTarget;
/**
* <p>
* Swing compatible drawing surface for images and graphics.
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class PixelCanvas implements RenderTarget {
final private int fImageWidth;
final private int fImageHeight;
final private BufferedImage fEarthImage2;
/**
* <p>
* Construct a canvas of the specified size.
*
* @param pWidth
* Width
* @param pHeight
* Height
*/
public PixelCanvas(int pWidth, int pHeight) {
fImageWidth = pWidth;
fImageHeight = pHeight;
fEarthImage2 = new BufferedImage(fImageWidth, fImageHeight, BufferedImage.TYPE_INT_RGB);
}
public Graphics2D getGraphics2D() {
return fEarthImage2.createGraphics();
}
public void setPixel(int pX, int pY, int pA, int pR, int pG, int pB) {
setPixel(pX, pY, new Color(pR, pG, pB, pA));
}
public void setPixel(int pX, int pY, Color pColor) {
fEarthImage2.setRGB(pX, pY, pColor.getRGB());
}
public int getImageWidth() {
return fImageWidth;
}
public int getImageHeight() {
return fImageHeight;
}
public boolean saveToImage(String pFileName, String pFormat) throws IOException {
return ImageIO.write(fEarthImage2, pFormat, new File(pFileName));
}
public void saveToImage(OutputStream os) throws IOException {
ImageIO.write(fEarthImage2, "png", os);
}
}

View File

@ -0,0 +1,46 @@
package com.ctreber.acearth.plugins;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import com.ctreber.acearth.ACearth;
import com.ctreber.acearth.gui.PixelCanvas;
import com.ctreber.acearth.projection.Projection;
/**
* <p></p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 6, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
abstract public class Plugin implements ActionListener
{
protected ACearth fParent;
protected boolean fActiveP = true;
protected Projection fProjection;
protected PixelCanvas fRenderTarget;
public void actionPerformed(ActionEvent e)
{
}
abstract public boolean hasGUIP();
abstract public void render();
public void setProjection(Projection pProjection)
{
fProjection = pProjection;
}
public void setRenderTarget(PixelCanvas pRenderTarget)
{
fRenderTarget = pRenderTarget;
}
public void setParent(ACearth pParent)
{
fParent = pParent;
}
}

View File

@ -0,0 +1,191 @@
package com.ctreber.acearth.plugins.markers;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.List;
import com.ctreber.acearth.gui.PixelCanvas;
import com.ctreber.acearth.projection.Projection;
import com.ctreber.acearth.projection.ProjectionOrtho;
import com.ctreber.acearth.util.Coordinate;
import com.ctreber.acearth.util.Point2D;
import com.ctreber.acearth.util.Point3D;
import com.ctreber.acearth.util.StringParser;
/**
* <p>
* Marks a location on the globe.
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class Marker {
private static final int MARKER_SIZE = 4;
// types of marker label alignment
private static final int MarkerAlignDefault = 0;
private static final int MarkerAlignLeft = 1;
private static final int MarkerAlignRight = 2;
private static final int MarkerAlignAbove = 3;
private static final int MarkerAlignBelow = 4;
private Marker(Coordinate pCoordinate, String pLabel, int pAlign) {
fCoordinate = pCoordinate;
fLabel = pLabel;
fAlign = pAlign;
}
private Coordinate fCoordinate;
private String fLabel;
private int fAlign;
// private static List fMarkers;
/*
* builtin_marker_data[] contains the "built-in" marker data that is
* compiled into AC.earth. (My apologies for misspellings, omissions of your
* favorite location, or geographic inaccuracies. This is primarily just a
* pile of data that I had handy instead of an attempt to provide a sample
* that is "globally correct" in some sense.)
*/
// public static List loadMarkerFile(String pFileName) throws IOException {
// fMarkers = new ArrayList();
//
// final LineNumberReader lReader = new LineNumberReader(new
// FileReader(pFileName));
// String lLine;
// while ((lLine = lReader.readLine()) != null) {
// processLine(lLine);
// }
//
// lReader.close();
//
// return fMarkers;
// }
//
// private static void processLine(String pLine) {
// final int lPos = pLine.indexOf('#');
// if (lPos != -1) {
// // Remove comment
// pLine = pLine.substring(0, lPos);
// }
//
// final Marker lMarkerInfo = createFromLine(pLine);
// if (lMarkerInfo != null) {
// fMarkers.add(lMarkerInfo);
// }
// }
private static Marker createFromLine(String pLine) {
final List lWords = StringParser.parse(pLine);
final double lLat = Double.parseDouble((String) lWords.get(0));
final double lLong = Double.parseDouble((String) lWords.get(1));
final String lLabel = (String) lWords.get(2);
int lAlign = MarkerAlignDefault;
if (lWords.size() >= 4) {
String lAlignString = (String) lWords.get(3);
if (lAlignString.equalsIgnoreCase("left")) {
lAlign = MarkerAlignLeft;
}
if (lAlignString.equalsIgnoreCase("right")) {
lAlign = MarkerAlignRight;
}
if (lAlignString.equalsIgnoreCase("above")) {
lAlign = MarkerAlignAbove;
}
if (lAlignString.equalsIgnoreCase("below")) {
lAlign = MarkerAlignBelow;
}
}
final Coordinate lPos = new Coordinate(lLat, lLong);
if (!lPos.check()) {
// ACearth.logError("latitude must be between -90 and 90, and
// longitude must be between -180 and 180");
return null;
}
return new Marker(lPos, lLabel, lAlign);
}
public String toString() {
return fLabel + " (" + fCoordinate + "), align: " + fAlign;
}
// --Recycle Bin START (10/28/02 2:24 PM):
// public String getLabel()
// {
// return fLabel;
// }
// --Recycle Bin STOP (10/28/02 2:24 PM)
// --Recycle Bin START (10/28/02 2:24 PM):
// public int getAlign()
// {
// return fAlign;
// }
// --Recycle Bin STOP (10/28/02 2:24 PM)
// --Recycle Bin START (10/28/02 2:24 PM):
// public Coordinate getLocation()
// {
// return fCoordinate;
// }
// --Recycle Bin STOP (10/28/02 2:24 PM)
public void render(PixelCanvas pCanvas, Projection pProjection) {
final Point3D lPos = pProjection.rotate(fCoordinate.getPoint3D());
if ((pProjection instanceof ProjectionOrtho) && (lPos.getZ() <= 0)) {
// Back side of the Earth.
// Insight: We don't need to check if the marker is visible in other
// projections because they always show the whole earth - and all
// markers!
return;
}
Point2D lPoint = pProjection.finalize(pProjection.project2D(lPos));
final int lX = (int) lPoint.getX();
final int lY = (int) lPoint.getY();
// Draw a circle
Graphics2D g2d = pCanvas.getGraphics2D();
g2d.setColor(Color.red);
// pCanvas.drawCircle(lX, lY, MARKER_SIZE);
g2d.drawOval(lX, lY, MARKER_SIZE, MARKER_SIZE);
if (fLabel != null) {
switch (fAlign) {
case Marker.MarkerAlignLeft:
break;
case Marker.MarkerAlignRight:
case Marker.MarkerAlignDefault:
// pCanvas.drawText(lX + MARKER_SIZE, lY + 4, fLabel);
// fRenderTarget.setTextFont(fRenderTarget.getTextFont().deriveFont(9.0f));
g2d.setFont(new Font("", Font.PLAIN, 9));
g2d.drawString(fLabel, lX + MARKER_SIZE + 1, lY + 2);
break;
case Marker.MarkerAlignAbove:
break;
case Marker.MarkerAlignBelow:
break;
}
}
}
public static Marker loadMarkerFile(String line) {
return createFromLine(line);
}
}

View File

@ -0,0 +1,74 @@
package com.ctreber.acearth.plugins.markers;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import com.ctreber.acearth.ACearth;
import com.ctreber.acearth.plugins.Plugin;
/**
* <p>
* Renders markers to the render target. a
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class PluginMarkers extends Plugin {
private List fMarkers;
// private String fMarkerFileName = ACearth.getHomeDir() +
// "markersDefault.txt";
public PluginMarkers(List<Marker> markers) {
// ACearth.indent("AC.earth Markers plug-in");
//
// ACearth.log("Reading markers");
//
// if(fMarkerFileName == null)
// {
// throw new RuntimeException("Marker file name not set");
// }
// try
// {
// fMarkers = Marker.loadMarkerFile(fMarkerFileName);
// } catch(IOException e)
// {
// ACearth.logError("Marker file not found");
// return;
// }
fMarkers = markers;
// ACearth.outdent();
}
public boolean hasGUIP() {
return false;
}
public void render() {
if (!fActiveP) {
return;
}
// fRenderTarget.setTextFont(fRenderTarget.getTextFont().deriveFont(9.0f));
Iterator lIt = fMarkers.iterator();
while (lIt.hasNext()) {
Marker lMarker = (Marker) lIt.next();
lMarker.render(fRenderTarget, fProjection);
}
}
// public void setMarkerFileName(String pMarkerFileName)
// {
// fMarkerFileName = pMarkerFileName;
// }
public String toString() {
return "AC.earth Markers plug-in";
}
}

View File

@ -0,0 +1,271 @@
package com.ctreber.acearth.projection;
import com.ctreber.acearth.util.*;
/**
* <p>A projection for a globe on a flat surface (must be subclassed).
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
abstract public class Projection
{
// Target information
int fImageHeight;
int fImageWidth;
private double fXOffset;
private double fYOffset;
//Viewing information
private int fShiftX;
private int fShiftY;
double fScale;
private Coordinate fViewPos;
/** <p>In rads */
private double fViewRotation;
double fViewMagnification;
//Transformation matrix parameters */
private double fCosLat;
private double fSinLat;
private double fCosLon;
private double fSinLon;
private double fCosRot;
private double fSinRot;
/**
* <p>Initialize transform parameters, set offset to center of image
* (plus shifts), set scale
*/
public void initTransformTable()
{
// Set transformation parameters
fCosLat = Math.cos(Toolkit.degsToRads(fViewPos.getLat()));
fSinLat = Math.sin(Toolkit.degsToRads(fViewPos.getLat()));
fCosLon = Math.cos(Toolkit.degsToRads(fViewPos.getLong()));
fSinLon = Math.sin(Toolkit.degsToRads(fViewPos.getLong()));
fCosRot = Math.cos(Toolkit.degsToRads(fViewRotation));
fSinRot = Math.sin(Toolkit.degsToRads(fViewRotation));
fXOffset = (double)fImageWidth / 2 + fShiftX;
fYOffset = (double)fImageHeight / 2 + fShiftY;
setScale();
}
abstract protected void setScale();
/**
* <p>Project 3D point on y axis.
*/
abstract public double projectY(double pY);
abstract public double inverseProjectY(double pY);
/**
* <p>Project 3D point on x axis.
*/
abstract protected double projectX(double pX, double pZ);
abstract public double inverseProjectX(double pX);
public abstract boolean isVisible(Point3D pPoint);
public boolean isWithinImage(Point2D pPoint)
{
return (pPoint.getX() >= 0) && (pPoint.getX() < fImageWidth) &&
(pPoint.getY() >= 0) && (pPoint.getY() < fImageHeight);
}
/**
* <p>Translate screen point into coordinate on Earth.
*
* @param pX
* @param pY
* @return
*/
abstract public Coordinate getLocation(int pX, int pY);
/**
* <p>Imagine view the globe, N is up, S is down, and N0E0 is in the center.
* x is right/left, y is up/down, and z is front/rear.
*
* <p>Map points are located on the surface of a unit sphere (diameter = 1).
* Latitude is the angle between x and y or z and y. Longitude is the angle
* between x and z.
*
* <p>Why? The way we choose our global coordinate system, longitude circles
* (latidude variable) always have the same size while the size of
* latidude circles (longitude variable) depends on the latitude.
*
* @param pPoint
* @return
*/
public Point2D project2D(Point3D pPoint)
{
return new Point2D(projectX(pPoint.getX(), pPoint.getZ()),
projectY(pPoint.getY()));
}
public Point2D finalize(Point2D pPoint)
{
return new Point2D(finalizeX(pPoint.getX()), finalizeY(pPoint.getY()));
}
/**
* <p>Since the final mapping is relative to the center of the image
* -PI and PI get mapped to the left and right border respectively.
* But see ProjectionOrtho.setScale().
*/
public double finalizeX(double pX)
{
return fXOffset + fScale * pX;
}
/**
* <p>Since the final mapping is relative to the center of the image
* -PI and PI get mapped to the bottom and top border respectively.
* But see ProjectionOrtho.setScale().
*/
public double finalizeY(double pY)
{
return fYOffset - fScale * pY;
}
/**
* <p>Transform screen to image coordinates.
*/
public double inverseFinalizeX(double x)
{
return (x - fXOffset) / fScale;
}
/**
* <p>Transform screen to image coordinates.
*/
public double inverseFinalizeY(double y)
{
return (fYOffset - y) / fScale;
}
/**
* <p>Rotate the point according to the current rotation of Earth.
*/
public Point3D rotate(Point3D pPoint)
{
double lX = pPoint.getX();
double lY = pPoint.getY();
double lZ = pPoint.getZ();
// Do NOT inline vars - it does not work (just inline _t_ for a try).
double _c_ = fCosLon;
double _s_ = fSinLon;
double _t_ = _c_ * lX - _s_ * lZ;
lZ = _s_ * lX + _c_ * lZ;
lX = _t_;
_c_ = fCosLat;
_s_ = fSinLat;
_t_ = (_c_ * lY) - (_s_ * lZ);
lZ = (_s_ * lY) + (_c_ * lZ);
lY = _t_;
_c_ = fCosRot;
_s_ = fSinRot;
_t_ = (_c_ * lX) - (_s_ * lY);
lY = (_s_ * lX) + (_c_ * lY);
lX = _t_;
return new Point3D(lX, lY, lZ);
}
public Point3D rotateReverse(Point3D pPoint)
{
// Set transformation parameters
final double fCosLat = Math.cos(Toolkit.degsToRads(-fViewPos.getLat()));
final double fSinLat = Math.sin(Toolkit.degsToRads(-fViewPos.getLat()));
final double fCosLon = Math.cos(Toolkit.degsToRads(-fViewPos.getLong()));
final double fSinLon = Math.sin(Toolkit.degsToRads(-fViewPos.getLong()));
final double fCosRot = Math.cos(Toolkit.degsToRads(-fViewRotation));
final double fSinRot = Math.sin(Toolkit.degsToRads(-fViewRotation));
double lX = pPoint.getX();
double lY = pPoint.getY();
double lZ = pPoint.getZ();
// Do NOT inline vars - it does not work (just inline lTmp for a try).
double lCosFac;
double lSinFac;
double lTmp;
// Note that the order of the three rotation had to be reversed as well.
lCosFac = fCosRot;
lSinFac = fSinRot;
lTmp = (lCosFac * lX) - (lSinFac * lY);
lY = (lSinFac * lX) + (lCosFac * lY);
lX = lTmp;
lCosFac = fCosLat;
lSinFac = fSinLat;
lTmp = (lCosFac * lY) - (lSinFac * lZ);
lZ = (lSinFac * lY) + (lCosFac * lZ);
lY = lTmp;
lCosFac = fCosLon;
lSinFac = fSinLon;
lTmp = (lCosFac * lX) - (lSinFac * lZ);
lZ = (lSinFac * lX) + (lCosFac * lZ);
lX = lTmp;
return new Point3D(lX, lY, lZ);
}
public double getScale()
{
return fScale;
}
public Coordinate getViewPos()
{
return fViewPos;
}
public void setViewMagnification(double pViewMagnification)
{
fViewMagnification = pViewMagnification;
setScale();
}
public void setViewPos(Coordinate pViewPos)
{
fViewPos = pViewPos;
}
public void setShiftX(int pX)
{
fShiftX = pX;
}
public void setShiftY(int pY)
{
fShiftY = pY;
}
public void setViewRotation(double pViewRotation)
{
fViewRotation = pViewRotation;
}
public void setImageWidth(int pImageWidth)
{
fImageWidth = pImageWidth;
}
public void setImageHeight(int pImageHeight)
{
fImageHeight = pImageHeight;
}
}

View File

@ -0,0 +1,72 @@
package com.ctreber.acearth.projection;
import com.ctreber.acearth.util.Coordinate;
import com.ctreber.acearth.util.Point3D;
/**
* <p>Cylindrical projection. Show Earth flatly spread out on rectangle.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class ProjectionCyl extends Projection
{
/**
* <p>All of Earth is visible.
*
* @param pPoint
* @return
*/
public boolean isVisible(Point3D pPoint)
{
return true;
}
public Coordinate getLocation(int pX, int pY)
{
final Coordinate lRaw = new Coordinate(Math.atan(inverseFinalizeY(pY)),
inverseFinalizeX(pX));
return rotateReverse(lRaw.getPoint3DRads()).getCoordinate();
}
/**
* <p>The scale is set so that a value of
* 2PI gets mapped to the full image width times the magnification.
* But see ProjectionOrtho.setScale().
*/
protected void setScale()
{
// Makes 2PI come out as full image width
fScale = fViewMagnification * fImageWidth / (2 * Math.PI);
}
/**
* @return Longitude (-PI to PI), linearly on x axis.
*/
public double projectX(double pX, double pZ)
{
return Math.atan2(pX, pZ);
}
public double inverseProjectX(double pX)
{
return Math.sin(pX);
}
/**
* @return Latitude (-PI/2 to PI/2), projected from center of Earth on
* y axis with a linear scale.
*/
public double projectY(double pY)
{
return (pY >= 0.9999999999) ? 1e6 :
(pY <= -0.9999999999) ? -1e6 : Math.tan(Math.asin(pY));
}
public double inverseProjectY(double y)
{
return Math.sin(Math.atan(y));
}
}

View File

@ -0,0 +1,75 @@
package com.ctreber.acearth.projection;
import com.ctreber.acearth.util.Coordinate;
import com.ctreber.acearth.util.Point3D;
/**
* <p>Mercator projection. Show Earth flatly spread out on rectangle.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class ProjectionMerc extends Projection
{
/**
* <p>All of Earth is visible.
*
* @param pPoint
* @return
*/
public boolean isVisible(Point3D pPoint)
{
return true;
}
public Coordinate getLocation(int pX, int pY)
{
final Coordinate lRaw = new Coordinate(
2 * (Math.atan(Math.exp(inverseFinalizeY(pY))) - Math.PI / 4),
inverseFinalizeX(pX));
return rotateReverse(lRaw.getPoint3DRads()).getCoordinate();
}
/**
* <p>The scale is set so that a value of
* 2PI gets mapped to the full image width times the magnification.
* But see ProjectionOrtho.setScale().
*/
protected void setScale()
{
// Makes 2PI come out as full image width
fScale = fViewMagnification * fImageWidth / (2 * Math.PI);
}
/**
* @return Longitude (-PI to PI), linearly on x axis.
*/
public double projectX(double pX, double pZ)
{
return Math.atan2(pX, pZ);
}
public double inverseProjectX(double pX)
{
return Math.sin(pX);
}
/**
* @return Latitude (-PI/2 to PI/2), projected from center of Earth on
* y axis with a twist and a log scale.
*/
public double projectY(double pY)
{
return (pY >= 0.9999999999) ? 1e6
: (pY <= -0.9999999999) ? -1e6
: Math.log(Math.tan(Math.asin(pY) / 2 + Math.PI / 4));
}
public double inverseProjectY(double y)
{
return Math.sin(2 * (Math.atan(Math.exp(y)) - Math.PI / 4));
}
}

View File

@ -0,0 +1,71 @@
package com.ctreber.acearth.projection;
import com.ctreber.acearth.util.Coordinate;
import com.ctreber.acearth.util.Point3D;
/**
* <p>Orthographic projection (show Earth as a ball).
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class ProjectionOrtho extends Projection
{
/**
* <p>All of Earth is visible.
*
* @param pPoint
* @return
*/
public boolean isVisible(Point3D pPoint)
{
return pPoint.getZ() >= 0;
}
public Coordinate getLocation(int pX, int pY)
{
final double lX = inverseFinalizeX(pX);
final double lY = inverseFinalizeY(pY);
final double lZ = Math.sqrt(1 - lX * lX - lY * lY);
final Point3D lP = new Point3D(lX, lY, lZ);
return rotateReverse(lP).getCoordinate();
}
/**
* @return Longitude, not in rad but from -1 to 1.
*/
public double projectX(double pX, double pZ)
{
return pX;
}
public double inverseProjectX(double pX)
{
return pX;
}
/**
* @return Latitude, not in rad but from -1 to 1.
*/
public double projectY(double pY)
{
return pY;
}
public double inverseProjectY(double pY)
{
return pY;
}
/**
* <p>The scale is not from -PI to PI but from -1 to 1 in this case
* (the range of x, y, z of the points).
*/
protected void setScale()
{
fScale = Math.min(fImageHeight, fImageWidth) * fViewMagnification * 0.99 / 2;
}
}

View File

@ -0,0 +1,24 @@
package com.ctreber.acearth.renderer;
import java.awt.Color;
/**
* <p>.
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*
*/
public interface RenderTarget {
public void setPixel(int pX, int pY, int pA, int pR, int pG, int pB);
public void setPixel(int pX, int pY, Color pColor);
public int getImageWidth();
public int getImageHeight();
}

View File

@ -0,0 +1,91 @@
package com.ctreber.acearth.renderer;
import java.awt.*;
import java.util.*;
import java.util.List;
import com.ctreber.acearth.shader.Shader;
/**
* <p>
* Uses defined RowTypeRenderers and Shader to render to render target.
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class Renderer {
private Shader fShader;
private RenderTarget fRenderTarget;
private List fRowTypeRenderers = new ArrayList();
public Renderer(RenderTarget pRenderTarget) {
fRenderTarget = pRenderTarget;
}
public void render() {
final Iterator lIt = fRowTypeRenderers.iterator();
while (lIt.hasNext()) {
RowTypeRenderer lRowRenderer = (RowTypeRenderer) lIt.next();
lRowRenderer.startNewRun();
}
renderRows();
}
private void renderRows() {
for (int lRowNo = 0; lRowNo < fRenderTarget.getImageHeight(); lRowNo++) {
int[] lPixelTypes = getPixelTypes(lRowNo);
renderRow(lRowNo, lPixelTypes);
}
}
/**
* <p>
* Get pixel types for whole row from all registered RowRenderers.
*
* @param pRowNo
* Row number.
* @return Pixel types for row.
*/
private int[] getPixelTypes(int pRowNo) {
// Create the types array
final int[] lPixelTypes = new int[fRenderTarget.getImageWidth()];
final Iterator lIt = fRowTypeRenderers.iterator();
while (lIt.hasNext()) {
RowTypeRenderer lRowRenderer = (RowTypeRenderer) lIt.next();
lRowRenderer.getPixelTypes(pRowNo, lPixelTypes);
}
return lPixelTypes;
}
/**
* <p>
* With help of Shader, render pixel types to actual colored pixels.
*
* @param pRowNo
* @param pPixelTypes
*/
private void renderRow(int pRowNo, int[] pPixelTypes) {
// For each pixel in row, render it.
final Color[] lPixelColors = fShader.getShadedColors(pRowNo, pPixelTypes);
for (int lColNo = 0; lColNo < fRenderTarget.getImageWidth(); lColNo++) {
fRenderTarget.setPixel(lColNo, pRowNo, lPixelColors[lColNo]);
}
}
public void setShader(Shader pShader) {
fShader = pShader;
}
public void setRenderTarget(RenderTarget pRenderTarget) {
fRenderTarget = pRenderTarget;
}
public void addRowTypeRenderer(RowTypeRenderer pRowRenderer) {
fRowTypeRenderers.add(pRowRenderer);
}
}

View File

@ -0,0 +1,26 @@
package com.ctreber.acearth.renderer;
/**
* <p>Renders a row of pixel types.</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 11, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public interface RowTypeRenderer
{
/**
* <p>Each time when rendering an image, call startNewRun() first.
*/
public void startNewRun();
/**
* <p>Set pixel type for specified row number. Note some pixel types
* might be already set. The renderer can build on this information
* or overwrite it.
*
* @param pRowNo
* @param pPixelTypes
*/
public void getPixelTypes(int pRowNo, final int[] pPixelTypes);
}

View File

@ -0,0 +1,82 @@
package com.ctreber.acearth.renderer;
import com.ctreber.acearth.scanbit.ScanBit;
import com.ctreber.acearth.scanbit.BitGeneratorMap;
/**
* <p>Renders a row of ScanBits to pixel types.</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 11, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class RowTypeRendererScanBit implements RowTypeRenderer
{
private int fScanBitIndex;
private ScanBit[] fScanBits;
private final int[] fScanToPixelType = new int[256];
public void startNewRun()
{
fScanBitIndex = 0;
generateScanToPixelTypeTable();
}
public void getPixelTypes(int pRowNo, int[] pPixelTypes)
{
// For all ScanBits in specified row...
while((fScanBitIndex < fScanBits.length) &&
(fScanBits[fScanBitIndex].getY() == pRowNo))
{
for(int i = fScanBits[fScanBitIndex].getlXFrom();
i <= fScanBits[fScanBitIndex].getXTo(); i++)
{
/**
* This is weird... why summing up the types? Note the row stays the
* same, but it possibly gets paved over a couple of times (There
* might be ScanBits painting on the same pixels).
*
* The polygons specify -1 as water and 1 as land.
* The type table says space is 0, Water is 1 to 64, Land is 65+.
*
* The outline paints the whole world as water (64). Adding a
* land pixel (1) creates a value of 65 (land). Adding a water
* pixel (-1) changes this back to 64 (water).
*/
pPixelTypes[i] += fScanBits[fScanBitIndex].getType();
}
fScanBitIndex++;
}
// Translate generateScanBits values into pixels types.
for(int lCol = 0; lCol < pPixelTypes.length; lCol++)
{
pPixelTypes[lCol] = fScanToPixelType[pPixelTypes[lCol] & 0xff];
}
}
private void generateScanToPixelTypeTable()
{
for(int i = 0; i < 256; i++)
{
if(i == 0)
{
// 0 is Space.
fScanToPixelType[i] = BitGeneratorMap.PixTypeSpace;
} else if(i > 64)
{
// Above 64 it's land.
fScanToPixelType[i] = BitGeneratorMap.PixTypeLand;
} else
{
// From 1 to 64 incl. it's water
fScanToPixelType[i] = BitGeneratorMap.PixTypeWater;
}
}
}
public void setScanBits(ScanBit[] pScanBits)
{
fScanBits = pScanBits;
}
}

View File

@ -0,0 +1,60 @@
package com.ctreber.acearth.renderer;
import com.ctreber.acearth.scanbit.BitGeneratorMap;
import com.ctreber.acearth.scandot.ScanDot;
/**
* <p>Renders a row of ScanDots to pixel types.</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 11, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class RowTypeRendererScanDot implements RowTypeRenderer
{
private int fScanDotIndex;
private ScanDot[] fScanDots;
public void startNewRun()
{
fScanDotIndex = 0;
}
public void getPixelTypes(int pRowNo, int[] pPixelTypes)
{
// For all ScanDots in specified row...
while((fScanDotIndex < fScanDots.length) &&
(fScanDots[fScanDotIndex].getY() == pRowNo))
{
ScanDot lDot = fScanDots[fScanDotIndex];
if(lDot.getType() == ScanDot.DotTypeStar)
{
if(pPixelTypes[lDot.getX()] == BitGeneratorMap.PixTypeSpace)
{
// Stars get only painted on Space.
pPixelTypes[lDot.getX()] = BitGeneratorMap.PixTypeStar;
}
} else
{
// The only other type for a dot (so far) is "grid".
switch(pPixelTypes[lDot.getX()])
{
case BitGeneratorMap.PixTypeLand:
pPixelTypes[lDot.getX()] = BitGeneratorMap.PixTypeGridLand;
break;
case BitGeneratorMap.PixTypeWater:
pPixelTypes[lDot.getX()] = BitGeneratorMap.PixTypeGridWater;
break;
}
}
fScanDotIndex++;
}
}
public void setScanDots(ScanDot[] pScanDots)
{
fScanDots = pScanDots;
}
}

View File

@ -0,0 +1,120 @@
package com.ctreber.acearth.scanbit;
import java.util.*;
import com.ctreber.acearth.projection.Projection;
import com.ctreber.acearth.util.*;
import com.ctreber.aclib.sort.CTSort;
import com.ctreber.aclib.sort.QuickSort;
/**
* <p>
* A BitGeneratorMap scans a map into ScanBits.
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*
*/
public abstract class BitGeneratorMap extends ScanBitGenerator {
// Types of pixels.
public static final int PixTypeSpace = 0;
public static final int PixTypeLand = 1;
public static final int PixTypeWater = 2;
public static final int PixTypeStar = 3;
public static final int PixTypeGridLand = 4;
public static final int PixTypeGridWater = 5;
// Parameters influencing generateScanBits buffer genertion.
private Polygon[] fMapData;
Projection fProjection;
private List fScanbitsVector = new ArrayList();
// Created by scanPolygon
List fEdgeCrossings;
abstract protected ScanBuf scanOutline();
abstract protected void handleCrossings(ScanBuf pScanBuf, EdgeCrossing[] pEdgeCrossings);
abstract protected Comparator getEdgeXingComparator();
abstract protected void scanPolygon(ScanBuf pScanBuf, Point3D[] pPoints3D, Point2D[] pPoints2D, int pIndex);
public BitGeneratorMap(Projection pProjection) {
fProjection = pProjection;
}
/**
* <p>
* Create outline for the map, scan all polygons.
*/
public void generateScanBits() {
// Prepare data.
fScanbitsVector = new ArrayList();
fProjection.setImageWidth(fImageWidth);
fProjection.setImageHeight(fImageHeight);
fProjection.initTransformTable();
// Trace outling and polygons.
final ScanBuf lScanBuf = scanOutline();
fScanbitsVector.addAll(lScanBuf.getScanbits(64));
scanPolygons();
// Dress results.
final CTSort lSort = new QuickSort();
fScanBitsArray = (ScanBit[]) fScanbitsVector.toArray(new ScanBit[0]);
lSort.sort(fScanBitsArray);
}
private void scanPolygons() {
for (int lPolyNo = 0; lPolyNo < fMapData.length; lPolyNo++) {
Polygon lPolygon = fMapData[lPolyNo];
Point3D[] lPoints3D = new Point3D[lPolygon.getSize()];
Point2D[] lPoints2D = new Point2D[lPolygon.getSize()];
transformPolygonPoints(lPolygon, lPoints3D, lPoints2D);
// For all points in polygon...
fEdgeCrossings = new ArrayList();
ScanBuf lScanBuf = new ScanBuf(fImageHeight, fImageWidth);
for (int i = 0; i < lPoints2D.length; i++) {
scanPolygon(lScanBuf, lPoints3D, lPoints2D, i);
}
if (fEdgeCrossings.size() > 0) {
// Edge crossings have been generated, deal with them.
final EdgeCrossing[] xings = (EdgeCrossing[]) fEdgeCrossings.toArray(new EdgeCrossing[0]);
final CTSort lSort = new QuickSort();
lSort.sort(xings, getEdgeXingComparator());
handleCrossings(lScanBuf, xings);
}
if (lScanBuf.containsPoints()) {
// Scan lines have been generated, transform them into ScanBit.
fScanbitsVector.addAll(lScanBuf.getScanbits(lPolygon.getType()));
}
}
}
/**
* The transformation rotates 3D and projects 2D points from it
*/
private void transformPolygonPoints(Polygon pPolygon, Point3D[] pPoints3D, Point2D[] pPoints2D) {
for (int i = 0; i < pPolygon.getPoints().length; i++) {
Point3D lPoint = pPolygon.getPoints()[i];
Point3D lPointRotated = fProjection.rotate(lPoint);
pPoints3D[i] = lPointRotated;
pPoints2D[i] = fProjection.project2D(lPointRotated);
}
}
public void setMapData(Polygon[] pMapData) {
fMapData = pMapData;
}
protected void addEdgeXing(EdgeCrossing pEdgeXing) {
fEdgeCrossings.add(pEdgeXing);
}
}

View File

@ -0,0 +1,263 @@
package com.ctreber.acearth.scanbit;
import java.util.Comparator;
import com.ctreber.acearth.projection.Projection;
import com.ctreber.acearth.util.*;
/**
* <p>Map scanner for mercator and cylindrical projections.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class BitGeneratorMapDefault extends BitGeneratorMap
{
public BitGeneratorMapDefault(Projection pProjection)
{
super(pProjection);
}
protected Comparator getEdgeXingComparator()
{
return new EdgeXingComparator();
}
/**
* Seems to: walk along outline of projected area.
*/
protected ScanBuf scanOutline()
{
final ScanBuf lScanBuf = new ScanBuf(fImageHeight, fImageWidth);
final double lLeft = fProjection.finalizeX(-Math.PI);
final double lRight = fProjection.finalizeX(Math.PI);
// Will be adjusted to fit height.
final double lTop = fProjection.finalizeY(1e6);
final double lBottom = fProjection.finalizeY(-1e6);
// Top
lScanBuf.addLine(lRight, lTop, lLeft, lTop);
// Left
lScanBuf.addLine(lLeft, lTop, lLeft, lBottom);
// Bottom
lScanBuf.addLine(lLeft, lBottom, lRight, lBottom);
// Right
lScanBuf.addLine(lRight, lBottom, lRight, lTop);
return lScanBuf;
}
/**
* <p>Look at 2 neighboring points in polygon and find edge crossings -
* edge of the globe?! Edge of image?
*/
protected void scanPolygon(ScanBuf pScanBuf,
Point3D[] pPoints3D, Point2D[] pPoints2D, int pIndex)
{
final Point2D lCurr = pPoints2D[pIndex];
final int lIndexPrev = pIndex - 1 >= 0 ? pIndex - 1 : pPoints2D.length - 1;
final Point2D lPrev = pPoints2D[lIndexPrev];
double dx = lCurr.getX() - lPrev.getX();
if(Math.abs(dx) <= Math.PI)
{
// Perimeter not crossed.
pScanBuf.addLine(
fProjection.finalizeX(lPrev.getX()), fProjection.finalizeY(lPrev.getY()),
fProjection.finalizeX(lCurr.getX()), fProjection.finalizeY(lCurr.getY()));
return;
}
// Perimeter crossed, we need to wrap the line around the edge.
int lAngle;
double mx;
double my = getYMidPoint(pPoints3D[lIndexPrev], pPoints3D[pIndex]);
if(dx > 0)
{
// Curve runs right
mx = -Math.PI;
lAngle = 2;
} else
{
mx = Math.PI;
lAngle = 0;
}
// From previous point to edge...
pScanBuf.addLine(
fProjection.finalizeX(lPrev.getX()), fProjection.finalizeY(lPrev.getY()),
fProjection.finalizeX(mx), fProjection.finalizeY(my));
addEdgeXing(new EdgeCrossing(EdgeCrossing.XingTypeExit, pIndex, mx, my, lAngle));
if(dx > 0)
{
mx = Math.PI;
lAngle = 0;
} else
{
mx = -Math.PI;
lAngle = 2;
}
// ...and from edge to current point.
pScanBuf.addLine(
fProjection.finalizeX(mx), fProjection.finalizeY(my),
fProjection.finalizeX(lCurr.getX()), fProjection.finalizeY(lCurr.getY()));
addEdgeXing(new EdgeCrossing(EdgeCrossing.XingTypeEntry, pIndex, mx, my, lAngle));
}
/**
* <p>My educated guess is that the mid point between the current and
* the previous point is calculated, and - kind of - y of that point
* is returned.
*/
private double getYMidPoint(Point3D pPrev, Point3D pCurr)
{
double lY;
final double lZ;
if(pCurr.getX() != 0)
{
// if xPrev is twice xCurr, ratio is 2
double ratio = (pPrev.getX() / pCurr.getX());
lY = pPrev.getY() - ratio * pCurr.getY();
lZ = pPrev.getZ() - ratio * pCurr.getZ();
} else
{
lY = pCurr.getY();
lZ = pCurr.getZ();
}
final double lDistance = Math.sqrt((lY * lY) + (lZ * lZ));
lY *= ((lZ > 0) ? -1 : 1) / lDistance;
return fProjection.projectY(lY);
}
/**
* <p>Side effect: Creates ScanBuf lines.
*/
protected void handleCrossings(ScanBuf pScanBuf, EdgeCrossing[] xings)
{
EdgeCrossing from;
EdgeCrossing to;
int lStart;
if(xings[0].getType() == EdgeCrossing.XingTypeExit)
{
lStart = 0;
} else
{
// Type "entry".
from = xings[xings.length - 1];
to = xings[0];
addEdgeToScanbuf(pScanBuf, from, to);
lStart = 1;
}
for(int i = lStart; i < xings.length - 1; i += 2)
{
from = xings[i];
to = xings[i + 1];
addEdgeToScanbuf(pScanBuf, from, to);
}
}
/**
* <p>For handleCrossing(). Side effect: Creates ScanBuf lines.
*
* @param pScanBuf
* @param from
* @param to
*/
private void addEdgeToScanbuf(ScanBuf pScanBuf, EdgeCrossing from,
EdgeCrossing to)
{
int lAngleFrom = (int)from.getAngle();
double lXFrom = fProjection.finalizeX(from.getX());
double lYFrom = fProjection.finalizeY(from.getY());
// Step around in 90 degree increments until target angle is reached
while(lAngleFrom != (int)to.getAngle())
{
int lAngleNew = 0;
double lXNew = 0;
double lYNew = 0;
switch(lAngleFrom)
{
case 0:
// Top right
lXNew = fProjection.finalizeX(Math.PI);
lYNew = fProjection.finalizeY(1e6);
lAngleNew = 1;
break;
case 1:
// Top left
lXNew = fProjection.finalizeX(-Math.PI);
lYNew = fProjection.finalizeY(1e6);
lAngleNew = 2;
break;
case 2:
// Bottom left
lXNew = fProjection.finalizeX(-Math.PI);
lYNew = fProjection.finalizeY(-1e6);
lAngleNew = 3;
break;
case 3:
// Bottom right
lXNew = fProjection.finalizeX(Math.PI);
lYNew = fProjection.finalizeY(-1e6);
lAngleNew = 0;
break;
}
pScanBuf.addLine(lXFrom, lYFrom, lXNew, lYNew);
lAngleFrom = lAngleNew;
lXFrom = lXNew;
lYFrom = lYNew;
}
// ...and from last to final.
pScanBuf.addLine(lXFrom, lYFrom, fProjection.finalizeX(to.getX()),
fProjection.finalizeY(to.getY()));
}
private static class EdgeXingComparator implements Comparator
{
public int compare(Object o1, Object o2)
{
final EdgeCrossing a = (EdgeCrossing)o1;
final EdgeCrossing b = (EdgeCrossing)o2;
if(a.getAngle() < b.getAngle())
{
return -1;
}
if(a.getAngle() > b.getAngle())
{
return 1;
}
// Angles are equal.
if(a.getAngle() == 0)
{
return (a.getY() < b.getY()) ? -1 : (a.getY() > b.getY()) ? 1 : 0;
}
if(a.getAngle() == 2)
{
return (a.getY() > b.getY()) ? -1 : (a.getY() < b.getY()) ? 1 : 0;
}
throw new RuntimeException("No result");
}
}
}

View File

@ -0,0 +1,168 @@
package com.ctreber.acearth.scanbit;
import java.util.Comparator;
import com.ctreber.acearth.projection.Projection;
import com.ctreber.acearth.util.*;
/**
* <p>Map scanner for orthographic projection.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class BitGeneratorMapOrtho extends BitGeneratorMap
{
public BitGeneratorMapOrtho(Projection pProjection)
{
super(pProjection);
}
protected Comparator getEdgeXingComparator()
{
return new EdgeCrossingComparator();
}
protected ScanBuf scanOutline()
{
final ScanBuf lScanBuf = new ScanBuf(fImageHeight, fImageWidth);
addArcToScanbuf(lScanBuf, 1.0, 0.0, 0.0, 1.0, 0.0, 2 * Math.PI);
return lScanBuf;
}
private void addArcToScanbuf(ScanBuf pScanBuf, double pXFrom, double pYFrom,
double pAngleFrom, double pXTo, double pYTo, double pAngleTo)
{
double step = 1 / fProjection.getScale() * 10;
if(step > 0.05)
{
step = 0.05;
}
final int lAngleFrom = (int)Math.ceil(pAngleFrom / step);
final int lAngleTo = (int)Math.floor(pAngleTo / step);
double prev_x = fProjection.finalizeX(pXFrom);
double prev_y = fProjection.finalizeY(pYFrom);
double curr_x;
double curr_y;
if(lAngleFrom <= lAngleTo)
{
double c_step = Math.cos(step);
double s_step = Math.sin(step);
double angle = lAngleFrom * step;
double arc_x = Math.cos(angle);
double arc_y = Math.sin(angle);
for(int i = lAngleFrom; i <= lAngleTo; i++)
{
curr_x = fProjection.finalizeX(arc_x);
curr_y = fProjection.finalizeY(arc_y);
pScanBuf.addLine(prev_x, prev_y, curr_x, curr_y);
/* instead of repeatedly calling cos() and sin() to get the next
* values for arc_x and arc_y, simply rotate the existing values
*/
double tmp = (c_step * arc_x) - (s_step * arc_y);
arc_y = (s_step * arc_x) + (c_step * arc_y);
arc_x = tmp;
prev_x = curr_x;
prev_y = curr_y;
}
}
curr_x = fProjection.finalizeX(pXTo);
curr_y = fProjection.finalizeY(pYTo);
pScanBuf.addLine(prev_x, prev_y, curr_x, curr_y);
}
protected void scanPolygon(ScanBuf pScanBuf,
Point3D[] pPoints3D, Point2D[] pPoints2D, int pIndex)
{
Point3D extra;
Point3D lCurr = pPoints3D[pIndex];
final int lIndexPrev = pIndex - 1 >= 0 ? pIndex - 1 : pPoints2D.length - 1;
Point3D lPrev = pPoints3D[lIndexPrev];
if(lPrev.getZ() <= 0)
{
if(lCurr.getZ() <= 0)
{
return;
}
// Previous point not visible, but current one is: horizon crossed.
extra = findEdgeCrossing(lPrev, lCurr);
addEdgeXing(new EdgeCrossing(EdgeCrossing.XingTypeEntry, pIndex,
extra.getX(), extra.getY(), Math.atan2(extra.getY(), extra.getX())));
lPrev = extra;
} else
{
if(lCurr.getZ() <= 0)
{
// Previous point visible, but current is not: horizon crossed.
extra = findEdgeCrossing(lPrev, lCurr);
addEdgeXing(new EdgeCrossing(EdgeCrossing.XingTypeExit, pIndex,
extra.getX(), extra.getY(), Math.atan2(extra.getY(), extra.getX())));
lCurr = extra;
}
}
pScanBuf.addLine(
fProjection.finalizeX(lPrev.getX()), fProjection.finalizeY(lPrev.getY()),
fProjection.finalizeX(lCurr.getX()), fProjection.finalizeY(lCurr.getY()));
}
private Point3D findEdgeCrossing(Point3D pPrev, Point3D pCurr)
{
double tmp = pCurr.getZ() / (pCurr.getZ() - pPrev.getZ());
final double r0 = pCurr.getX() - tmp * (pCurr.getX() - pPrev.getX());
final double r1 = pCurr.getY() - tmp * (pCurr.getY() - pPrev.getY());
tmp = Math.sqrt((r0 * r0) + (r1 * r1));
return new Point3D(r0 / tmp, r1 / tmp, 0);
}
protected void handleCrossings(ScanBuf pScanBuf, EdgeCrossing[] xings)
{
EdgeCrossing from;
EdgeCrossing to;
int lStart;
if(xings[0].getType() == EdgeCrossing.XingTypeExit)
{
lStart = 0;
} else
{
from = xings[xings.length - 1];
to = xings[0];
addArcToScanbuf(pScanBuf, from.getX(), from.getY(), from.getAngle(),
to.getX(), to.getY(), to.getAngle() + 2 * Math.PI);
lStart = 1;
}
for(int i = lStart; i < xings.length - 1; i += 2)
{
from = xings[i];
to = xings[i + 1];
addArcToScanbuf(pScanBuf, from.getX(), from.getY(), from.getAngle(),
to.getX(), to.getY(), to.getAngle());
}
}
private static class EdgeCrossingComparator implements Comparator
{
public int compare(Object o1, Object o2)
{
final EdgeCrossing a = (EdgeCrossing)o1;
final EdgeCrossing b = (EdgeCrossing)o2;
return (a.getAngle() < b.getAngle()) ? -1 : (a.getAngle() > b.getAngle()) ? 1 : 0;
}
}
}

View File

@ -0,0 +1,62 @@
package com.ctreber.acearth.scanbit;
/**
* <p>Instruction to paint points xFrom to xTo on line y.
*
* <p>What I don't understand: why do values get summed to determine the
* pixel type?
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class ScanBit implements Comparable
{
private int fY;
private int lXFrom;
private int fXTo;
private int fType;
public ScanBit(int pY, int pLoX, int pHiX, int pType)
{
fY = pY;
lXFrom = pLoX;
fXTo = pHiX;
fType = pType;
}
public int compareTo(Object o)
{
if(o instanceof ScanBit)
{
ScanBit lOther = (ScanBit)o;
return (fY > lOther.fY) ? 1 : (fY < lOther.fY) ? -1 : 0;
}
throw new IllegalArgumentException("Can't compare with " + o.getClass());
}
public int getY()
{
return fY;
}
public int getlXFrom()
{
return lXFrom;
}
public int getXTo()
{
return fXTo;
}
/**
* <p>See values for
* @see com.ctreber.acearth.util.Polygon
*/
public int getType()
{
return fType;
}
}

View File

@ -0,0 +1,32 @@
package com.ctreber.acearth.scanbit;
/**
* <p>A ScanBitGenerator produces ScanBits.</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 11, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
abstract public class ScanBitGenerator
{
int fImageHeight;
int fImageWidth;
protected ScanBit[] fScanBitsArray;
abstract public void generateScanBits();
public void setImageHeight(int pImageHeight)
{
fImageHeight = pImageHeight;
}
public void setImageWidth(int pImageWidth)
{
fImageWidth = pImageWidth;
}
public ScanBit[] getScanBits()
{
return fScanBitsArray;
}
}

View File

@ -0,0 +1,193 @@
package com.ctreber.acearth.scanbit;
import java.util.ArrayList;
import java.util.List;
import com.ctreber.aclib.sort.CTSort;
import com.ctreber.aclib.sort.QuickSort;
/**
* <p>For each line, the scanbuffer (= a raster divice) records the points hit
* I.e., line 5 (y=5) contains the values 2, 6, 40, and 46 (these line have
* been crossed). The values always come as pairs because we're dealing with
* polygons, which have a left and a right side which consists of a line.
* The points in between two values painted as filled.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
class ScanBuf
{
private List[] fScanbuf;
private int fLineMin;
private int fLineMax;
private final int fLines;
private final int fPoints;
private boolean fScanBufsAdded;
/**
* <p>Create a list for each line.
*
* @param pLines Number of lines aka screen height.
* @param pPoints Number of points per line aka screen width.
*/
public ScanBuf(int pLines, int pPoints)
{
fLines = pLines;
fPoints = pPoints;
fLineMin = Integer.MAX_VALUE;
fLineMax = Integer.MIN_VALUE;
fScanBufsAdded = false;
fScanbuf = new ArrayList[fLines];
for(int i = 0; i < fScanbuf.length; i++)
{
fScanbuf[i] = new ArrayList();
}
}
/**
* <p>Add a line to the generateScanBits buffer.
*/
public void addLine(double pXFrom, double pYFrom, double pXTo, double pYTo)
{
int lYFrom;
int lYTo;
// Do some rounding (but not in the way we expect it), limit values
if(pYFrom < pYTo)
{
// Round lYFrom (but .5 is handled oddly)
// 1.5001 - 2.5 -> 1.0001 - 2.0 -> 2
lYFrom = (int)Math.ceil(pYFrom - 0.5);
// Round lYTo, substract 1
// 1.5 - 2.4999 -> 1.0 - 1.9999 -> 1
lYTo = (int)Math.floor(pYTo - 0.5);
/**
* I don't know if this is intended, but in Java 3 == 3.001 is false
* (the left arg is converted to double), so the expr is true only when
* pYTo - 0.5 is exactly lYTo
*/
if(lYTo == pYTo - 0.5)
{
lYTo--;
}
} else
{
lYFrom = (int)Math.ceil(pYTo - 0.5);
lYTo = (int)Math.floor(pYFrom - 0.5);
if(lYTo == pYFrom - 0.5)
{
lYTo--;
}
}
// Limit y to size of image
if(lYFrom < 0)
{
lYFrom = 0;
}
if(lYTo >= fLines)
{
lYTo = fLines - 1;
}
if(lYFrom > lYTo)
{
// No lines crossed.
return;
}
// Note min/max settings so far
if(lYFrom < fLineMin)
{
fLineMin = lYFrom;
}
if(lYTo > fLineMax)
{
fLineMax = lYTo;
}
// todo Curious: What happens if yFrom and yTo are equal? Shit? Or can't they be?
double lDx = (pXTo - pXFrom) / (pYTo - pYFrom);
double lX = pXFrom + lDx * ((lYFrom + 0.5) - pYFrom);
// Record the x value for every line (y).
for(int lLineNo = lYFrom; lLineNo <= lYTo; lLineNo++)
{
fScanbuf[lLineNo].add(new Double(lX));
lX += lDx;
}
fScanBufsAdded = true;
}
public boolean containsPoints()
{
return fScanBufsAdded;
}
/**
* <p>For each line, for each x value pair in line, create one ScanBit.
*/
public List getScanbits(int pCurveType)
{
final List fScanBits = new ArrayList();
// For each generateScanBits line containing points
for(int lLineNo = fLineMin; lLineNo <= fLineMax; lLineNo++)
{
// Sort so that lowest x values come first.
Double[] lScanLine = (Double[])fScanbuf[lLineNo].toArray(new Double[0]);
CTSort lSort = new QuickSort();
lSort.sort(lScanLine);
// The length will be divisible by 2 because we render closed polyons,
// so every generateScanBits line is crossed twice (left and right edge of polygon,
// no intersections allowed!).
for(int n = 0; n < lScanLine.length; n += 2)
{
// Round lLineFrom (but .5 is handled oddly)
// 1.5001 - 2.5 -> 1.0001 - 2.0 -> 2
int lXLo = (int)Math.ceil(lScanLine[n].doubleValue() - 0.5);
// Round lLineTo, substract 1
// 1.5 - 2.4999 -> 1.0 - 1.9999 -> 1
int lXHi = (int)Math.floor(lScanLine[n + 1].doubleValue() - 0.5);
// Limit low and high x to image dimensions
if(lXLo < 0)
{
lXLo = 0;
}
if(lXHi >= fPoints)
{
lXHi = fPoints - 1;
}
if(lXLo <= lXHi)
{
/**
* Shouldn't that always be true since we sorted? "Yes", BUT the
* rounding might create lo 3.6 -> 4.0 and hi 3.7 -> 3.0
*/
fScanBits.add(new ScanBit(lLineNo, lXLo, lXHi, pCurveType));
}
}
}
return fScanBits;
}
public int getYMax()
{
return fLineMax;
}
public int getYMin()
{
return fLineMin;
}
}

View File

@ -0,0 +1,70 @@
package com.ctreber.acearth.scandot;
import com.ctreber.acearth.projection.Projection;
import com.ctreber.acearth.util.*;
/**
* <p>Generate latitude and longitude grid as dots.
*
* <p>Refactored 08.11.2002
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class DotGeneratorLines extends ScanDotGenerator
{
private Projection fProjection;
private int fLineDivider;
private int fPointDivider;
private static final double PI = Math.PI;
private static final double TWOPI = 2 * PI;
private static final double HALFPI = PI / 2;
public DotGeneratorLines(Projection pProjection,
int pLineDevider, int pPointDivider)
{
fProjection = pProjection;
fLineDivider = pLineDevider;
fPointDivider = pPointDivider;
}
/**
* <p>Paint grid.
*/
public void generateScanDots()
{
double lLonStep = TWOPI / (fLineDivider * 4);
double lLatStep = PI / (fLineDivider * 2 * fPointDivider);
for(double lLon = -PI; lLon <= PI; lLon += lLonStep)
{
for(double lLat = -HALFPI; lLat <= HALFPI; lLat += lLatStep)
{
transformAndAddDot(new Coordinate(lLat, lLon));
}
}
lLatStep = TWOPI / (fLineDivider * 4);
lLonStep = PI / (fLineDivider * 2 * fPointDivider);
for(double lLat = -HALFPI; lLat <= HALFPI; lLat += lLatStep)
{
for(double lLon = -PI; lLon <= PI; lLon += lLonStep)
{
transformAndAddDot(new Coordinate(lLat, lLon));
}
}
}
private void transformAndAddDot(Coordinate pPos)
{
final Point3D lPointRotated = fProjection.rotate(pPos.getPoint3DRads());
if(fProjection.isVisible(lPointRotated))
{
Point2D lPoint = fProjection.finalize(fProjection.project2D(lPointRotated));
if(fProjection.isWithinImage(lPoint))
{
fDots.add(new ScanDot(ScanDot.DotTypeGrid, lPoint));
}
}
}
}

View File

@ -0,0 +1,51 @@
package com.ctreber.acearth.scandot;
import java.util.Random;
/**
* <p>Generate random stars as dots.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class DotGeneratorStars extends ScanDotGenerator
{
private final int fImageWidth;
private final int fImageHeight;
private int fBigStars;
private double fStarFrequency;
private final Random lRandom;
public DotGeneratorStars(int pWidth, int pHeight,
double pStarFrequency, int pBigStars, Random rnd)
{
lRandom = rnd;
fImageWidth = pWidth;
fImageHeight = pHeight;
fStarFrequency = pStarFrequency;
fBigStars = pBigStars;
}
public void generateScanDots()
{
// Make sure stars don't jump around between updates.
// final Random lRandom = new Random(ACearth.getStartTime());
final int lStarsMax = (int)(fImageWidth * fImageHeight * fStarFrequency);
for(int i = 0; i < lStarsMax; i++)
{
// "-1" to leave space for big stars.
int x = (int)(lRandom.nextDouble() * (fImageWidth - 1));
int y = (int)(lRandom.nextDouble() * fImageHeight);
fDots.add(new ScanDot(ScanDot.DotTypeStar, x, y));
// A big star is just two pixels wide.
if((fBigStars != 0) && (Math.random() * 100 < fBigStars))
{
fDots.add(new ScanDot(ScanDot.DotTypeStar, x + 1, y));
}
}
}
}

View File

@ -0,0 +1,67 @@
package com.ctreber.acearth.scandot;
import com.ctreber.acearth.util.Point2D;
/**
* <p>A single scandot (opposed to a Polygon).
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class ScanDot implements Comparable
{
// types of dots
public static final int DotTypeStar = 0;
public static final int DotTypeGrid = 1;
private int fX;
private int fY;
private int fType;
public ScanDot(int pType, int pX, int pY)
{
fType = pType;
fX = pX;
fY = pY;
}
public ScanDot(int pType, Point2D pPoint)
{
fType = pType;
fX = (int)pPoint.getX();
fY = (int)pPoint.getY();
}
public int compareTo(Object o)
{
if(o instanceof ScanDot)
{
ScanDot lOther = (ScanDot)o;
return fY > lOther.fY ? 1 : (fY < lOther.fY ? -1 : 0);
}
throw new IllegalArgumentException("Can't compare to " + o.getClass());
}
public int getType()
{
return fType;
}
public int getX()
{
return fX;
}
public int getY()
{
return fY;
}
public String toString()
{
return fX + ", " + fY + ": " + fType;
}
}

View File

@ -0,0 +1,27 @@
package com.ctreber.acearth.scandot;
import java.util.ArrayList;
import java.util.List;
/**
* <p>A ScanDotGenerator produces ScanDots.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
abstract public class ScanDotGenerator
{
List fDots = new ArrayList();
/**
* <p>Generate whatever dots are generated.
*/
abstract public void generateScanDots();
public List getScanDots()
{
return fDots;
}
}

View File

@ -0,0 +1,131 @@
package com.ctreber.acearth.shader;
import java.awt.*;
import com.ctreber.acearth.projection.Projection;
import com.ctreber.acearth.scanbit.BitGeneratorMap;
import com.ctreber.acearth.util.Coordinate;
import com.ctreber.acearth.util.Point3D;
/**
* <p>A shader computes Colors for a row of pixel types, depending
* on lighting parameters.</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 11, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
abstract public class Shader
{
private static final Color COLOR_SPACE = Color.black;
private static final Color COLOR_STAR = Color.white;
private static final Color COLOR_WATER = Color.blue;
private static final Color COLOR_LAND = Color.green;
// Brown
//static final Color COLOR_LAND = new Color(255, 136, 25);
private static final Color COLOR_GRID_LAND = Color.white;
// Bright blue
private static final Color COLOR_GRID_WATER = new Color(128, 128, 255);
/** <p>Needed to calculate lighting vectors. */
Projection fProjection;
// Stuff below only needed when shading.
private Coordinate fSunPos;
private double fNightSideBrightness;
private double fDaySideBrightness;
private double fTerminatorDiscontinuity;
private double fDaySideValueBase;
private double fDaySideValueRange;
Point3D fLightVector;
abstract public Color[] getShadedColors(int pRowNo, int[] pRowTypes);
public void init()
{
// Precompute shading parameters. I personally find the terminator
// stuff is obscure and might as well be left out.
final double tmp = fTerminatorDiscontinuity / 100;
// 100%: day, 0%: night
fDaySideValueBase = (int)(tmp * fDaySideBrightness +
(1 - tmp) * fNightSideBrightness);
fDaySideValueRange = fDaySideBrightness - fDaySideValueBase;
fLightVector = fProjection.rotate(fSunPos.getPoint3D());
}
Color getShadedColorForType(int pType, double pSunValue)
{
double lBrightness;
if(pSunValue < 0)
{
// The sun is below the horizon.
lBrightness = fNightSideBrightness / 100;
} else
{
// The sun is above the horizon. The brightness will range from
// the base to the maximum value.
lBrightness = (fDaySideValueBase + pSunValue * fDaySideValueRange) / 100;
}
if(lBrightness > 1.0)
{
lBrightness = 1.0;
}
switch(pType)
{
case BitGeneratorMap.PixTypeSpace:
return COLOR_SPACE;
case BitGeneratorMap.PixTypeStar:
return COLOR_STAR;
case BitGeneratorMap.PixTypeGridLand:
return shade(COLOR_GRID_LAND, lBrightness);
case BitGeneratorMap.PixTypeGridWater:
return shade(COLOR_GRID_WATER, lBrightness);
case BitGeneratorMap.PixTypeLand:
return shade(COLOR_LAND, lBrightness);
case BitGeneratorMap.PixTypeWater:
return shade(COLOR_WATER, lBrightness);
}
return null;
}
private static Color shade(Color pColor, double pBrightness)
{
return new Color((int)(pColor.getRed() * pBrightness),
(int)(pColor.getGreen() * pBrightness),
(int)(pColor.getBlue() * pBrightness));
}
public void setProjection(Projection pProjection)
{
fProjection = pProjection;
}
public void setSunPos(Coordinate pSunPos)
{
fSunPos = pSunPos;
}
public void setDaySideBrightness(double pDaySideBrightness)
{
fDaySideBrightness = pDaySideBrightness;
}
public void setNightSideBrightness(double pNightSideBrightness)
{
fNightSideBrightness = pNightSideBrightness;
}
public void setTerminatorDiscontinuity(double pTerminatorDiscontinuity)
{
fTerminatorDiscontinuity = pTerminatorDiscontinuity;
}
}

View File

@ -0,0 +1,59 @@
package com.ctreber.acearth.shader;
import java.awt.*;
/**
* <p>Shader for projections which display the whole surface.</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 11, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class ShaderDefault extends Shader
{
public Color[] getShadedColors(int pRowNo, int[] pRowTypes)
{
final double y = fProjection.inverseProjectY(fProjection.inverseFinalizeY(pRowNo));
// conceptually, on each iteration of the i loop, we want:
//
// x = Math.sin(INV_XPROJECT(i)) * sqrt(1 - (y*y));
// z = cos(INV_XPROJECT(i)) * sqrt(1 - (y*y));
//
// computing this directly is rather expensive, however, so we only
// compute the first (i=0) pair of values directly; all other pairs
// (i>0) are obtained through successive rotations of the original
// pair (by inv_proj_scale radians).
//
// compute initial (x, z) values
double tmp = Math.sqrt(1 - (y * y));
double x = Math.sin(fProjection.inverseFinalizeX(0)) * tmp;
double z = Math.cos(fProjection.inverseFinalizeX(0)) * tmp;
// compute rotation coefficients used
// to find subsequent (x, z) values
tmp = 1 / fProjection.getScale();
final double sin_theta = Math.sin(tmp);
final double cos_theta = Math.cos(tmp);
// save a little computation in the inner loop
final double lYBySunVectorY = y * fLightVector.getY();
// use i_lim to encourage compilers to register loop limit
final Color[] lColors = new Color[pRowTypes.length];
for(int lColNo = 0; lColNo < pRowTypes.length; lColNo++)
{
double lSunValue = (x * fLightVector.getX()) + lYBySunVectorY +
(z * fLightVector.getZ());
lColors[lColNo] = getShadedColorForType(pRowTypes[lColNo], lSunValue);
// compute next (x, z) values via 2-d rotation
tmp = (cos_theta * z) - (sin_theta * x);
x = (sin_theta * z) + (cos_theta * x);
z = tmp;
}
return lColors;
}
}

View File

@ -0,0 +1,24 @@
package com.ctreber.acearth.shader;
import java.awt.*;
/**
* <p>Flat shader (does not care for Projection).</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 11, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class ShaderFlat extends Shader
{
public Color[] getShadedColors(int pRowNo, int[] pRowTypes)
{
final Color[] lColors = new Color[pRowTypes.length];
for(int i = 0; i < pRowTypes.length; i++)
{
lColors[i] = getShadedColorForType(pRowTypes[i], 1.0);
}
return lColors;
}
}

View File

@ -0,0 +1,55 @@
package com.ctreber.acearth.shader;
import java.awt.*;
/**
* <p>Shader for the orthographic projection.</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com (Nov 11, 2002)</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class ShaderOrtho extends Shader
{
private static double[] fXValues;
public Color[] getShadedColors(int pRowNo, int[] pRowTypes)
{
if(pRowNo == 0)
{
fXValues = computeXValues(pRowTypes.length);
}
final double y = fProjection.inverseProjectY(fProjection.inverseFinalizeY(pRowNo));
final double tmp = 1 - (y * y);
final double lYBySunVectorY = y * fLightVector.getY();
final Color[] lColors = new Color[pRowTypes.length];
for(int lColNo = 0; lColNo < pRowTypes.length; lColNo++)
{
double x = fXValues[lColNo];
double z = Math.sqrt(tmp - (x * x));
double lSunValue = (x * fLightVector.getX()) + lYBySunVectorY + (z * fLightVector.getZ());
lColors[lColNo] = getShadedColorForType(pRowTypes[lColNo], lSunValue);
}
return lColors;
}
/**
*
* @return X value for each column in image.
*/
private double[] computeXValues(int pWidth)
{
final double[] lTable = new double[pWidth];
for(int lColNo = 0; lColNo < pWidth; lColNo++)
{
lTable[lColNo] = fProjection.inverseProjectX(fProjection.inverseFinalizeX(lColNo));
}
return lTable;
}
}

View File

@ -0,0 +1,153 @@
package com.ctreber.acearth.util;
import java.io.IOException;
import java.io.Writer;
/**
* <p>
* Latitude and longitude coordinate. Can be used as declination and right
* ascension as well.
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class Coordinate {
/*
* MeanObliquity gives the mean obliquity of the earth's axis at epoch
* 1990.0 (computed as 23.440592 degrees according to the method given in
* duffett-smith, section 27)
*/
private static final double MEAN_OBLIQUITY = 23.440592 * Toolkit.TWOPI / 360;
// Or DE
private double fLat;
// Or RA
private double fLong;
public Coordinate() {
}
/**
* <p>
* Construct a location specfied by two angles. Your choice if in degrees or
* rads, but keep track!
*
* @param pLong
* Longitude or RA
* @param pLat
* Latitude or DE
*/
public Coordinate(double pLat, double pLong) {
fLat = pLat;
fLong = pLong;
}
public void renderAsXML(Writer writer) throws IOException {
writer.write("<Coordinate>\n");
writer.write(" <latitude>" + fLat + "</latitude>\n");
writer.write(" <longitude>" + fLong + "</longitude>\n");
writer.write("</Coordinate>\n");
}
public Point3D getPoint3D() {
final double lLatRad = Toolkit.degsToRads(fLat);
final double lLongRad = Toolkit.degsToRads(fLong);
final double lX = Math.cos(lLatRad) * Math.sin(lLongRad);
final double lY = Math.sin(lLatRad);
final double lZ = Math.cos(lLatRad) * Math.cos(lLongRad);
return new Point3D(lX, lY, lZ);
}
/**
* <p>
* Assumes coordinate is not in degrees but rads.
*
* @return
*/
public Point3D getPoint3DRads() {
final double lX = Math.cos(fLat) * Math.sin(fLong);
final double lY = Math.sin(fLat);
final double lZ = Math.cos(fLat) * Math.cos(fLong);
return new Point3D(lX, lY, lZ);
}
/**
* <p>
* Convert from ecliptic to equatorial coordinates (after duffett-smith,
* section 27)
*/
public Coordinate eclipticToEquatorial() {
final double sin_e = Math.sin(MEAN_OBLIQUITY);
final double cos_e = Math.cos(MEAN_OBLIQUITY);
final double lRA = Math.atan2(Math.sin(fLong) * cos_e - Math.tan(fLat) * sin_e, Math.cos(fLong));
final double lDE = Math.asin(Math.sin(fLat) * cos_e + Math.cos(fLat) * sin_e * Math.sin(fLong));
return new Coordinate(lDE, lRA);
}
/**
* <p>
* Add position to this position, make sure coordinates are valid.
*/
public void add(Coordinate lOther) {
fLat += lOther.fLat;
fLong += lOther.fLong;
wrap();
}
/**
* <p>
* Warp coordinates exceeding valid values. Happens when latitudes and
* longitudes are added or substracted.
*/
public void wrap() {
if (fLat > 90) {
fLat = 180 - fLat;
fLong += 180;
} else if (fLat < -90) {
fLat = -180 - fLat;
fLong += 180;
}
if (fLong > 180) {
do {
fLong -= 360;
} while (fLong > 180);
} else if (fLong < -180) {
do {
fLong += 360;
} while (fLong < -180);
}
}
public double getLat() {
return fLat;
}
public double getDE() {
return fLat;
}
public double getLong() {
return fLong;
}
public double getRA() {
return fLong;
}
public boolean check() {
return (-90 <= fLat) && (fLat <= 90) && (-180 <= fLong) && (fLong <= 180);
}
public String toString() {
return "lat: " + fLat + ", long: " + fLong;
}
}

View File

@ -0,0 +1,59 @@
package com.ctreber.acearth.util;
/**
* <p>Holds information about a line crossing "the edge of Earth".
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class EdgeCrossing
{
public static final int XingTypeEntry = 0;
public static final int XingTypeExit = 1;
private int fType;
private int fIndex;
private double fX;
private double fY;
private double fAngle;
public EdgeCrossing(int pType, int pIndex, double pX, double pY, double pAngle)
{
fType = pType;
fX = pX;
fY = pY;
fAngle = pAngle;
fIndex = pIndex;
}
public String toString()
{
return fType + ": " + fX + ", " + fY + ", " + fAngle + " (" + fIndex + ")";
}
public int getType()
{
return fType;
}
public double getX()
{
return fX;
}
public double getY()
{
return fY;
}
public double getAngle()
{
return fAngle;
}
public int getIndex()
{
return fIndex;
}
}

View File

@ -0,0 +1,35 @@
package com.ctreber.acearth.util;
/**
* <p>A point in a 2 axis space.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class Point2D
{
private double fX;
private double fY;
public Point2D(double pX, double pY)
{
fX = pX;
fY = pY;
}
public double getX()
{
return fX;
}
public double getY()
{
return fY;
}
public String toString()
{
return "x: " + fX + ", y: " + fY;
}
}

View File

@ -0,0 +1,48 @@
package com.ctreber.acearth.util;
/**
* <p>A point in a 2 axis space.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class Point3D
{
private double fX;
private double fY;
private double fZ;
public Point3D(double pX, double pY, double pZ)
{
fX = pX;
fY = pY;
fZ = pZ;
}
public double getX()
{
return fX;
}
public double getY()
{
return fY;
}
public double getZ()
{
return fZ;
}
public String toString()
{
return "x: " + fX + ", y: " + fY + ", z: " + fZ;
}
public Coordinate getCoordinate()
{
return new Coordinate(Toolkit.radsToDegs(Math.asin(fY)),
Toolkit.radsToDegs(Math.atan2(fX, fZ)));
}
}

View File

@ -0,0 +1,49 @@
package com.ctreber.acearth.util;
/**
* <p>A polygon in a 3 axis space.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class Polygon
{
public static final int LAND = 1;
public static final int WATER = -1;
private int fType;
private Point3D[] fPoints;
public Polygon(int pType, Point3D[] pPoints)
{
fType = pType;
fPoints = pPoints;
}
public int getType()
{
return fType;
}
public Point3D[] getPoints()
{
return fPoints;
}
public Point3D getPoint(int pIndex)
{
return fPoints[pIndex];
}
public int getSize()
{
return fPoints.length;
}
public String toString()
{
return "Type " + fType + ", " + fPoints.length + " points";
}
}

View File

@ -0,0 +1,99 @@
package com.ctreber.acearth.util;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Cuts a string in words separated by white space. Quotes and square
* brackets are recognized (that is, white space within is ignored).
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class StringParser
{
public static List parse(String pLine)
{
final List lSections = new ArrayList();
// True if within word.
boolean lInSectionP = false;
// Current char
char lChar;
// Wait for this character before switching back to normal parsing.
char lSeparator = ' ';
// Part count.
int lSectionNo = 0;
// Part start position
int lSectionStart = 0;
// Part end position
int lSectionEnd = 0;
final int lLen = pLine.length();
for(int lCharNo = 0; lCharNo <= lLen; lCharNo++)
{
if(lCharNo < lLen)
{
lChar = pLine.charAt(lCharNo);
} else
{
// This is a fictional last character.
lChar = ' ';
}
if(lInSectionP)
{
// In section. Termination is by space or specific separator.
if((lChar != ' ') || (lSeparator != ' '))
{
// It's not a space, or it is a space, but we wait for a special separator.
if(lChar == lSeparator)
{
// We waited for this separator. Switch back to normal parsing.
lSeparator = ' ';
lSectionEnd = lCharNo - 1;
} else
{
lSectionEnd = lCharNo;
}
} else
{
// Section has ended (with a space).
lSections.add(pLine.substring(lSectionStart, lSectionEnd + 1));
lSectionNo++;
lInSectionP = false;
}
} else
{
// Not in a section, skipping white space.
if(lChar != ' ')
{
// No white space: a section has started.
if(lChar == '"')
{
// Special parsing "string"
lSeparator = '"';
lSectionStart = lCharNo + 1;
} else if(lChar == '[')
{
// Special parsing "square brackets"
lSeparator = ']';
lSectionStart = lCharNo + 1;
} else
{
// Use normal parsing.
lSeparator = ' ';
lSectionEnd = lSectionStart = lCharNo;
}
lInSectionP = true;
} else
{
// More void...
}
}
}
return lSections;
}
}

View File

@ -0,0 +1,258 @@
package com.ctreber.acearth.util;
import java.util.*;
/**
* <p>Calculates the position of the point on Earth which is directly
* below the sun or the moon.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class SunPositionCalculator
{
/*
* the epoch upon which these astronomical calculations are based is
* 1990 january 0.0, 631065600 seconds since the beginning of the
* "unix epoch" (00:00:00 GMT, Jan. 1, 1970)
*
* given a number of seconds since the start of the unix epoch,
* daysSinceEpoch() computes the number of days since the start of the
* astronomical epoch (1990 january 0.0)
*/
private static final long EPOCH_START = 631065600000l;
/*
* assuming the apparent orbit of the sun about the earth is circular,
* the rate at which the orbit progresses is given by RadsPerDay --
* TWOPI radians per orbit divided by 365.242191 days per year:
*/
private static final double RADS_PER_DAY = Toolkit.TWOPI / 365.242191;
/*
* details of sun's apparent orbit at epoch 1990.0 (after
* duffett-smith, table 6, section 46)
*
* Epsilon_g (ecliptic longitude at epoch 1990.0) 279.403303 degrees
* OmegaBar_g (ecliptic longitude of perigee) 282.768422 degrees
* Eccentricity (eccentricity of orbit) 0.016713
*/
private static final double EPSILON_G = Toolkit.degsToRads(279.403303);
private static final double OMEGA_BAR_G = Toolkit.degsToRads(282.768422);
private static final double ECCENTRICITY = 0.016713;
/*
* Lunar parameters, epoch January 0, 1990.0
*/
private static final double MOON_MEAN_LONGITUDE = Toolkit.degsToRads(318.351648);
private static final double MOON_MEAN_LONGITUDE_PERIGEE = Toolkit.degsToRads(36.340410);
private static final double MOON_MEAN_LONGITUDE_NODE = Toolkit.degsToRads(318.510107);
private static final double MOON_INCLINATION = Toolkit.degsToRads(5.145396);
private static final double SIDERAL_MONTH = 27.3217;
/**
* <p>Calculate the position of the mean sun: where the sun would
* be if the earth's orbit were circular instead of ellipictal.
*
* <p>Verified.
*
* @param pDays days since ephemeris epoch
*/
private static double getMeanSunLongitude(double pDays)
{
double N, M;
N = RADS_PER_DAY * pDays;
N = Toolkit.fmod(N, 0, Toolkit.TWOPI);
if(N < 0)
{
N += Toolkit.TWOPI;
}
M = N + EPSILON_G - OMEGA_BAR_G;
if(M < 0)
{
M += Toolkit.TWOPI;
}
return M;
}
/**
* <p>Compute ecliptic longitude of sun (in radians)
* (after duffett-smith, section 47)
*
* <p>Verified.
*
* @param pMillis Milliseconds since unix epoch
*/
private static double getSunEclipticLongitude(long pMillis)
{
final double lDays = daysSinceEpoch(pMillis);
final double M_sun = getMeanSunLongitude(lDays);
final double E = doKepler(M_sun);
final double v = 2 * Math.atan(Math.sqrt((1 + ECCENTRICITY) / (1 - ECCENTRICITY)) * Math.tan(E / 2));
return (v + OMEGA_BAR_G);
}
static double daysSinceEpoch(long pMillis)
{
return (double)(pMillis - EPOCH_START) / 24 / 3600 / 1000;
}
/**
* solve Kepler's equation via Newton's method
* (after duffett-smith, section 47)
*
* <p>Verified.
*/
private static double doKepler(double M)
{
double E;
double lDelta;
E = M;
while(true)
{
lDelta = E - ECCENTRICITY * Math.sin(E) - M;
if(Math.abs(lDelta) <= 1e-10)
{
break;
}
E -= lDelta / (1 - ECCENTRICITY * Math.cos(E));
}
return E;
}
/**
* <p>computing julian dates (assuming gregorian calendar, thus this is
* only valid for dates of 1582 oct 15 or later)
* (after duffett-smith, section 4)
*
* <p>Verified.
*
* @param pYear year (e.g. 19xx)
* @param pMonth month (jan=1, feb=2, ...)
* @param pDay day of month
*/
private static double getJulianDate(int pYear, int pMonth, int pDay)
{
if((pMonth == 1) || (pMonth == 2))
{
pYear -= 1;
pMonth += 12;
}
final int A = pYear / 100;
final int B = 2 - A + (A / 4);
final int C = (int)(365.25 * pYear);
final int D = (int)(30.6001 * (pMonth + 1));
return B + C + D + pDay + 1720994.5;
}
/**
* <p>compute greenwich mean sidereal time (getGST) corresponding to a given
* number of milliseconds since the unix epoch
* (after duffett-smith, section 12)
*
* <p>Verified.
*/
private static double getGST(long pMillis)
{
final Calendar lCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
lCal.setTime(new Date(pMillis));
final double lJulianDate = getJulianDate(lCal.get(Calendar.YEAR), lCal.get(Calendar.MONTH) + 1,
lCal.get(Calendar.DAY_OF_MONTH));
final double T = (lJulianDate - 2451545) / 36525;
double T0 = ((T + 2.5862e-5) * T + 2400.051336) * T + 6.697374558;
T0 = Toolkit.fmod(T0, 0, 24.0);
if(T0 < 0)
{
T0 += 24;
}
final double UT = lCal.get(Calendar.HOUR_OF_DAY) +
(lCal.get(Calendar.MINUTE) + lCal.get(Calendar.SECOND) / 60.0) / 60.0;
T0 += UT * 1.002737909;
T0 = Toolkit.fmod(T0, 0, 24.0);
if(T0 < 0)
{
T0 += 24;
}
return T0;
}
/**
* <p>Given a particular time (expressed in milliseconds since the unix
* epoch), compute position on the earth (lat, lon) such that sun is
* directly overhead.
*
* <p>Verified.
*
* @param pMillis seconds since unix epoch
*
*/
public static Coordinate getSunPositionOnEarth(long pMillis)
{
final Coordinate lSunPosEc = new Coordinate(0.0, getSunEclipticLongitude(pMillis));
final Coordinate lSunPosEq = lSunPosEc.eclipticToEquatorial();
final double lRA = Toolkit.limitRads(lSunPosEq.getRA() - (Toolkit.TWOPI / 24) * getGST(pMillis));
return new Coordinate(Toolkit.radsToDegs(lSunPosEq.getDE()), Toolkit.radsToDegs(lRA));
}
/**
* <p>Given a particular time (expressed in milliseconds since the unix
* epoch), compute position on the earth (lat, lon) such that the
* moon is directly overhead.
*
* Based on duffett-smith **2nd ed** section 61; combines some steps
* into single expressions to reduce the number of extra variables.
*
* <p>Verified.
*/
public static Coordinate getMoonPositionOnEarth(long pMillis)
{
final double lDays = daysSinceEpoch(pMillis);
double lSunLongEc = getSunEclipticLongitude(pMillis);
final double Ms = getMeanSunLongitude(lDays);
double L = Toolkit.limitRads(Toolkit.fmod(lDays / SIDERAL_MONTH, 0, 1.0) * Toolkit.TWOPI + MOON_MEAN_LONGITUDE);
double Mm = Toolkit.limitRads(L - Toolkit.degsToRads(0.1114041 * lDays) - MOON_MEAN_LONGITUDE_PERIGEE);
double N = Toolkit.limitRads(MOON_MEAN_LONGITUDE_NODE - Toolkit.degsToRads(0.0529539 * lDays));
final double Ev = Toolkit.degsToRads(1.2739) * Math.sin(2.0 * (L - lSunLongEc) - Mm);
final double Ae = Toolkit.degsToRads(0.1858) * Math.sin(Ms);
Mm += Ev - Ae - Toolkit.degsToRads(0.37) * Math.sin(Ms);
final double Ec = Toolkit.degsToRads(6.2886) * Math.sin(Mm);
L += Ev + Ec - Ae + Toolkit.degsToRads(0.214) * Math.sin(2.0 * Mm);
L += Toolkit.degsToRads(0.6583) * Math.sin(2.0 * (L - lSunLongEc));
N -= Toolkit.degsToRads(0.16) * Math.sin(Ms);
L -= N;
lSunLongEc = Toolkit.limitRads((Math.abs(Math.cos(L)) < 1e-12) ?
(N + Math.sin(L) * Math.cos(MOON_INCLINATION) * Math.PI / 2) :
(N + Math.atan2(Math.sin(L) * Math.cos(MOON_INCLINATION), Math.cos(L))));
final double lSunLatEc = Math.asin(Math.sin(L) * Math.sin(MOON_INCLINATION));
final Coordinate lSunPosEq = new Coordinate(lSunLatEc, lSunLongEc).eclipticToEquatorial();
final double lRA = Toolkit.limitRads(lSunPosEq.getRA() - (Toolkit.TWOPI / 24) * getGST(pMillis));
return new Coordinate(Toolkit.radsToDegs(lSunPosEq.getDE()), Toolkit.radsToDegs(lRA));
}
}

View File

@ -0,0 +1,135 @@
package com.ctreber.acearth.util;
import java.util.HashSet;
import java.util.StringTokenizer;
/**
* <p>Some tools.
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class Toolkit
{
public static final double TWOPI = Math.PI * 2;
public static final double PI = Math.PI;
public static final double HALFPI = Math.PI / 2;
private static final HashSet fsNoCap;
static
{
fsNoCap = new HashSet();
fsNoCap.add("a");
fsNoCap.add("as");
fsNoCap.add("to");
fsNoCap.add("of");
fsNoCap.add("the");
fsNoCap.add("off");
fsNoCap.add("and");
fsNoCap.add("mid");
}
public static double degsToRads(double pDegrees)
{
return pDegrees * TWOPI / 360;
}
public static double radsToDegs(double pRadians)
{
return pRadians * 360 / TWOPI;
}
/**
* Force an angular value into the range [-PI, +PI]
*/
public static double limitRads(double x)
{
return fmod(x, -Math.PI, Math.PI);
}
/**
* <p>Verified.
*/
public static double fmod(double pValue, double pMod)
{
while(pValue < 0)
{
pValue += pMod;
}
while(pValue > pMod)
{
pValue -= pMod;
}
return pValue;
}
/**
* <p>Examples: min -2, max 2: range 4
*
* <ul>
* <li> value 1: lFact = 0
* <li> value 3: lFact = 1, value -1
* <li> value 9: lFact = 2, value 1
* <li> value -3: lFact = -1, value 1
* </ul>
*/
public static double fmod(double pValue, double pMinValue, double pMaxValue)
{
final double lRange = pMaxValue - pMinValue;
int lFact = (int)((pValue - pMinValue) / lRange);
if(pValue < pMinValue)
{
lFact--;
}
return pValue - lFact * lRange;
}
/**
* <p>Capitalize String. Uppercase words smaller/equal than 3 chars,
* lowercase defined exceptions. Capitalize within word after '.' and '-'.
* Capitalize all others.
*/
public static String intelligentCapitalize(String pText)
{
boolean lDoCap = false;
final StringTokenizer lST = new StringTokenizer(pText, ".- ", true);
final StringBuffer lSB = new StringBuffer(50);
while(lST.hasMoreTokens())
{
String lWord = lST.nextToken();
if(lWord.equals(".") || lWord.equals("-"))
{
lDoCap = true;
lSB.append(lWord);
continue;
}
if(lWord.equals(" "))
{
lDoCap = false;
lSB.append(lWord);
continue;
}
if(lDoCap || (lWord.length() > 3))
{
lSB.append(Character.toUpperCase(lWord.charAt(0)));
lSB.append(lWord.substring(1).toLowerCase());
} else
{
if(fsNoCap.contains(lWord.toLowerCase()))
{
lSB.append(lWord.toLowerCase());
} else
{
lSB.append(lWord.toUpperCase());
}
}
}
return lSB.toString();
}
}

View File

@ -0,0 +1,38 @@
package com.ctreber.aclib.gui;
/**
* <p></p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class MOBoolean extends MonitoredObject
{
private boolean fBoolean;
public MOBoolean()
{
}
public MOBoolean(boolean pBoolean)
{
fBoolean = pBoolean;
}
public void set(boolean pValue)
{
fBoolean = pValue;
fireValueChanged();
}
public boolean get()
{
return fBoolean;
}
public boolean checkRange()
{
return true;
}
}

View File

@ -0,0 +1,13 @@
package com.ctreber.aclib.gui;
/**
* <p>Implemented by classes interetested in MonitoredObject values changes.</p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public interface MOChangeListener
{
public void valueChanged(MonitoredObject pObject);
}

View File

@ -0,0 +1,74 @@
package com.ctreber.aclib.gui;
/**
* <p></p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class MODouble extends MonitoredObject
{
private double fDouble;
private boolean fCheckRange = false;
private double fMin;
private double fMax;
public MODouble()
{
}
public MODouble(double pDouble)
{
fDouble = pDouble;
}
public MODouble(double pDouble, double pMin, double pMax)
{
fMin = pMin;
fMax = pMax;
fCheckRange = true;
set(pDouble);
}
public void set(double pDouble)
{
if(!checkRange(pDouble))
{
throw new IllegalArgumentException("Argument '" + pDouble +
"' out of range [" + niceFormat(fMin) + "; " + niceFormat(fMax) + "]");
}
fDouble = pDouble;
fireValueChanged();
}
private static String niceFormat(double pDouble)
{
if(pDouble == Double.MAX_VALUE)
{
return "Infinity";
}
if(pDouble == Double.MIN_VALUE)
{
return "-Infinity";
}
return Double.toString(pDouble);
}
public double get()
{
return fDouble;
}
private boolean checkRange(double pDouble)
{
return !fCheckRange || (fMin <= pDouble) && (pDouble <= fMax);
}
public boolean checkRange()
{
return checkRange(fDouble);
}
}

View File

@ -0,0 +1,86 @@
package com.ctreber.aclib.gui;
import java.util.HashSet;
/**
* <p>
* Monitored enumeration value.
* </p>
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
* </p>
*
* @author Christian Treber, ct@ctreber.com
*
*/
public class MOEnum extends MonitoredObject {
private HashSet fValidValues = new HashSet();
/**
* <p>
* null if no value selected
*/
private Object fValue;
public void addValidValue(Object pValue) {
fValidValues.add(pValue);
}
public void set(Object pValue) {
if (pValue != null) {
checkValue(pValue);
}
fValue = pValue;
fireValueChanged();
}
public Object get() {
return fValue;
}
public boolean is(Object pObject) {
checkValue(pObject);
return this.equals(pObject);
}
public int hashCode() {
if (fValue == null) {
return 0;
}
return fValue.hashCode();
}
private void checkValue(Object pValue) {
if (!fValidValues.contains(pValue)) {
throw new IllegalArgumentException("Illegal enum value '" + pValue + "'");
}
}
public boolean equals(Object obj) {
if (obj instanceof MOEnum) {
MOEnum lOther = (MOEnum) obj;
if (fValue == null) {
return lOther.fValue == null;
}
return fValue.equals(lOther.fValue);
}
if (fValue == null) {
return obj.equals(null);
}
return fValue.equals(obj);
}
public HashSet getValidValues() {
return fValidValues;
}
public boolean checkRange() {
return true;
}
}

View File

@ -0,0 +1,74 @@
package com.ctreber.aclib.gui;
/**
* <p></p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class MOInteger extends MonitoredObject
{
private int fInteger;
private boolean fCheckRange = false;
private int fMin;
private int fMax;
public MOInteger()
{
}
public MOInteger(int pInteger)
{
fInteger = pInteger;
}
public MOInteger(int pInteger, int pMin, int pMax)
{
fMin = pMin;
fMax = pMax;
fCheckRange = true;
set(pInteger);
}
public void set(int pInteger)
{
if(!checkRange(pInteger))
{
throw new IllegalArgumentException("Argument '" + pInteger +
"' out of range [" + niceFormat(fMin) + "; " + niceFormat(fMax) + "]");
}
fInteger = pInteger;
fireValueChanged();
}
private static String niceFormat(int pInteger)
{
if(pInteger == Integer.MAX_VALUE)
{
return "Infinity";
}
if(pInteger == Integer.MIN_VALUE)
{
return "-Infinity";
}
return Integer.toString(pInteger);
}
public int get()
{
return fInteger;
}
private boolean checkRange(int pInteger)
{
return !fCheckRange || (fMin <= pInteger) && (pInteger <= fMax);
}
public boolean checkRange()
{
return checkRange(fInteger);
}
}

View File

@ -0,0 +1,36 @@
package com.ctreber.aclib.gui;
/**
* <p></p>
*
* <p>&copy; 2002 Christian Treber, ct@ctreber.com</p>
* @author Christian Treber, ct@ctreber.com
*
*/
public class MOString extends MonitoredObject
{
private String fString;
public MOString(String pString)
{
fString = pString;
}
public void set(String pString)
{
fString = pString;
fireValueChanged();
}
public String get()
{
return fString;
}
public boolean checkRange()
{
return true;
}
}

View File

@ -0,0 +1,44 @@
package com.ctreber.aclib.gui;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* <p>
* </p>
*
* <p>
* &copy; 2002 Christian Treber, ct@ctreber.com
* </p>
*
* @author Christian Treber, ct@ctreber.com
*
*/
abstract public class MonitoredObject {
private List fListeners = new ArrayList();
public void addChangeListener(MOChangeListener pListener) {
fListeners.add(pListener);
}
public void removeChangeListener(MOChangeListener pListener) {
fListeners.remove(pListener);
}
void fireValueChanged() {
final Iterator lIt = fListeners.iterator();
while (lIt.hasNext()) {
MOChangeListener lListener = (MOChangeListener) lIt.next();
lListener.valueChanged(this);
}
}
/**
* <p>
* Check value agains (possibly defined) constraints.
*
* @return True if value is within range or range is not checked.
*/
abstract public boolean checkRange();
}

View File

@ -0,0 +1,20 @@
package com.ctreber.aclib.sort;
import java.util.Comparator;
/**
* <p>Teehee - found that Comparator allready exists.
*
* &copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
abstract public class CTSort
{
public void sort(Object[] items)
{
sort(items, new DefaultComparator());
}
abstract public void sort(Object[] items, Comparator comparator);
}

View File

@ -0,0 +1,19 @@
package com.ctreber.aclib.sort;
import java.util.Comparator;
/**
* <p>Implements a default Comparator based on Comparable and a ascending
* sort order. Requires that the two objects are Comparable.
*
* &copy; 2002 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*
*/
public class DefaultComparator implements Comparator
{
public int compare(Object o1, Object o2)
{
return ((Comparable)o1).compareTo(o2);
}
}

View File

@ -0,0 +1,96 @@
package com.ctreber.aclib.sort;
import java.util.Comparator;
/**
* &copy; 2001 Christian Treber, ct@ctreber.com
* @author Christian Treber, ct@ctreber.com
*/
public class QuickSort extends CTSort
{
public void sort(Object[] items, Comparator comparator)
{
if(items.length <= 1)
{
// Nothing to sort t all or only one element.
return;
}
qsort(items, comparator, 0, items.length - 1);
insertionSort(items, comparator, 0, items.length - 1);
}
private void qsort(Object[] items, Comparator comparator, int l, int r)
{
final int M = 4;
int i;
int j;
Object v;
if((r - l) > M)
{
i = (r + l) / 2;
if(comparator.compare(items[l], items[i]) > 0)
{
swap(items, l, i);
}
if(comparator.compare(items[l], items[r]) > 0)
{
swap(items, l, r);
}
if(comparator.compare(items[i], items[r]) > 0)
{
swap(items, i, r);
}
j = r - 1;
swap(items, i, j);
i = l;
v = items[j];
while(true)
{
while(comparator.compare(items[++i], v) < 0)
{
}
while(comparator.compare(items[--j], v) > 0)
{
}
if(j < i)
{
break;
}
swap(items, i, j);
}
swap(items, i, r - 1);
qsort(items, comparator, l, j);
qsort(items, comparator, i + 1, r);
}
}
private static void swap(Object[] items, int i, int j)
{
final Object tmp;
tmp = items[i];
items[i] = items[j];
items[j] = tmp;
}
private static void insertionSort(Object[] items, Comparator comparator, int lo0, int hi0)
{
int i;
int j;
Object v;
for(i = lo0 + 1; i <= hi0; i++)
{
v = items[i];
j = i;
while((j > lo0) && (comparator.compare(items[j - 1], v) > 0))
{
items[j] = items[j - 1];
j--;
}
items[j] = v;
}
}
}

View File

@ -0,0 +1,228 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import jcckit.graphic.GraphicalElement;
import jcckit.graphic.Renderer;
import jcckit.plot.Plot;
import jcckit.plot.PlotCanvas;
import jcckit.renderer.Graphics2DRenderer;
import jcckit.renderer.Transformation;
import jcckit.util.ConfigParameters;
import jcckit.util.Factory;
/**
* Class which handles plotting into a <tt>Graphics</tt> context based on the
* {@link jcckit.renderer.GraphicsRenderer}. This class is not a subclass of
* <tt>java.awt.Component</tt>. The actual AWT component presenting the plot
* is an innerclass. Its instance wrapped by <tt>GraphicsPlotCanvas</tt> can
* be obtained with {@link #getGraphicsCanvas}.
* <p>
* The plot is painted by using double-buffering and pre-rendered view of the
* coordinate system. That is, the coordinate system is drawn into an off-screen
* image. It will be redrawn only if the size of the embedding AWT component is
* changed.
*
* @author Franz-Josef Elmer
*/
public class GraphicsPlotCanvas extends PlotCanvas {
/** Key of a configuration parameter. */
public static final String BACKGROUND_KEY = "background";
public static final String FOREGROUND_KEY = "foreground";
public static final String DOUBLE_BUFFERING_KEY = "doubleBuffering";
/**
* Class which does the actual painting. Needs the <tt>Component</tt> into
* which the plot is painted for some resources like size, background color,
* etc.
*
* @author Franz-Josef Elmer
*/
private final BufferedImage img3;
private final Graphics2D g3;
private Transformation _transformation;
private String _renderer = "jcckit.renderer.GraphicsRenderer";
private GraphicalElement _marker;
/**
* Creates an instance from the specified configuration parameters. <table
* border=1 cellpadding=5>
* <tr>
* <th>Key &amp; Default Value</th>
* <th>Type</th>
* <th>Mandatory</th>
* <th>Description</th>
* </tr>
* <tr>
* <td><tt>background = </tt><i>default background color of the wrapped
* AWT component</i></td>
* <td><tt>Color</tt></td>
* <td>no</td>
* <td>Background color of the wrapped AWT component.</td>
* </tr>
* <tr>
* <td><tt>foreground = </tt><i>default foreground color of the wrapped
* AWT component</i></td>
* <td><tt>Color</tt></td>
* <td>no</td>
* <td>Foreground color of the wrapped AWT component.</td>
* </tr>
* <tr>
* <td><tt>doubleBuffering = true</td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>If <tt>true</tt> the plot will be painted by using
* double-buffering and pre-rendered view of the coordinate system.
* </td></tr>
* </table>
* In addition the configuration parameters of the
* <a href="plot/PlotCanvas.html#PlotCanvas(jcckit.util.ConfigParameters)">
* constructor</a> of the superclass {@link jcckit.plot.PlotCanvas} apply.
*/
public GraphicsPlotCanvas(ConfigParameters config, BufferedImage img3) {
super(config);
this.img3 = img3;
setRenderer("jcckit.renderer.Graphics2DRenderer");
g3 = img3.createGraphics();
g3.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// _doubleBuffering = config.getBoolean(DOUBLE_BUFFERING_KEY, true);
background = config.getColor(BACKGROUND_KEY, Color.WHITE);
foreground = config.getColor(FOREGROUND_KEY, Color.BLACK);
}
private final Color background;
private final Color foreground;
/**
* Paints the plot. If {@link GraphicsPlotCanvas#_doubleBuffering} is set
* double-buffering and pre-rendered view of the coordinate system is used.
*/
public void paint() {
Dimension size = new Dimension(img3.getWidth(), img3.getHeight());
g3.setColor(background);
g3.fillRect(0, 0, size.width + 1, size.height + 1);
init(size);
_transformation.apply(g3);
Plot plot = getPlot();
drawCoordinateSystem(size, plot);
drawPlot(plot);
if (_marker != null) {
_marker.renderWith(createRenderer());
}
}
private void drawPlot(Plot plot) {
prepare();
Renderer renderer = createRenderer();
GraphicalElement[] curves = plot.getCurves();
for (int i = 0; i < curves.length; i++) {
curves[i].renderWith(renderer);
}
GraphicalElement annotation = plot.getAnnotation();
if (annotation != null) {
annotation.renderWith(renderer);
}
if (plot.isLegendVisible()) {
plot.getLegend().renderWith(renderer);
}
}
private void init(Dimension size) {
calculateTransformation(size);
}
private void drawCoordinateSystem(Dimension size, Plot plot) {
g3.setColor(foreground);
plot.getCoordinateSystem().renderWith(createRenderer());
}
/**
* Prepare graphics context before drawing the pre-rendered view of the
* coordinate system. Does nothing but will be used in subclasses.
*/
protected void prepare() {
}
/**
* Calculate the transformation form device-independent coordinates into
* device-dependent coordinates according to the specified canvas size.
*/
protected void calculateTransformation(Dimension size) {
_transformation = new Transformation(size.width, size.height, getPaper(), getHorizontalAnchor(),
getVerticalAnchor());
}
/**
* Creates an appropriated {@link Renderer} for the specified
* <tt>Graphics</tt> context.
*/
protected Renderer createRenderer() {
return ((Graphics2DRenderer) Factory.create(_renderer)).init(g3);
// return ((GraphicsRenderer) Factory.create(_renderer)).init(g, null,
// _transformation);
}
/**
* Sets the renderer used to render the plot. The default value is
* {@link GraphicsRenderer}.
*
* @param className
* Fully qualified name of the renderer class.
*/
public void setRenderer(String className) {
_renderer = className;
}
// /**
// * Maps the cursor position onto a point in device-independent
// coordinates.
// *
// * @param x
// * X-coordinate of the cursor.
// * @param y
// * Y-coordinate of the cursor.
// */
// public GraphPoint mapCursorPosition(int x, int y) {
// return _transformation.transformBack(x, y);
// }
/**
* Defines a graphical marker which will be drawn on top of the plot. To
* remove the marker call this method with argument <tt>null</tt>.
*
* @param marker
* Marker element. Can be <tt>null</tt>.
*/
public void setMarker(GraphicalElement marker) {
_marker = marker;
}
}

View File

@ -0,0 +1,182 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.data;
import java.text.*;
import java.util.*;
/**
* Abstract superclass of all data containers. A data container holds an
* ordered list of {@link DataElement DataElements} of the same type.
* <p>
* Data elements can be added, inserted, removed, or replaced.
* Such an action leads to a {@link DataEvent} which will be delivered to
* all {@link DataListener DataListeners} observing this
* <code>DataContainer</code>. If this data container also implements
* {@link DataEvent} (as {@link DataCurve} does) also the listeners
* registrated at the data container containg this container will be notified.
* As a consequence a <tt>DataListener</tt> must only be registered at the
* {@link DataPlot} instance and it will automatically also received events
* caused by manipulating one of its <tt>DataCurves</tt>.
* <p>
* Concrete subclasses have to implement {@link #isValid} which
* checks whether the added or inserted <tt>DataElement</tt> is of the right
* type. This is an application of the Template Method Design Pattern.
*
* @author Franz-Josef Elmer
*/
public abstract class DataContainer {
private final static String TEMPLATE
= "Invalid operation: {0}, Element: {1}, Container: {2}";
final static String ADD = "add",
REPLACE = "replace",
INSERT = "insert";
private final Vector _listeners = new Vector();
private final Vector _container = new Vector();
/** Adds a {@link DataListener}. Does nothing if already added. */
public void addDataListener(DataListener listener) {
if (!_listeners.contains(listener)) {
_listeners.addElement(listener);
}
}
/** Removes a {@link DataListener}. Does nothing if already removed. */
public void removeDataListener(DataListener listener) {
_listeners.removeElement(listener);
}
private void notifyListeners(DataEvent event) {
for (int i = 0, n = _listeners.size(); i < n; i++) {
((DataListener) _listeners.elementAt(i)).dataChanged(event);
}
// Notifies also parent container
if (this instanceof DataElement) {
DataContainer container = ((DataElement) this).getContainer();
if (container != null) {
container.notifyListeners(event);
}
}
}
/** Returns the number of elements of this container. */
public int getNumberOfElements() {
return _container.size();
}
/** Returns the element for the specified index. */
public DataElement getElement(int index) {
return (DataElement) _container.elementAt(index);
}
/**
* Returns the index of the specified element.
* @param element Element to be looked for.
* @return -1 if not found.
*/
public int getIndexOf(DataElement element) {
return _container.indexOf(element);
}
/**
* Adds a {@link DataElement}. After the element has been successfully
* added all {@link DataListener DataListeners} will be informed.
* @param element DataElement to be added.
* @throws IllegalArgumentException if <tt>element</tt> is not of the correct
* type which will be checked by the method {@link #isValid}.
*/
public void addElement(DataElement element) {
if (isValid(element)) {
_container.addElement(element);
element.setContainer(this);
notifyListeners(DataEvent.createAddEvent(this));
} else {
throwException(ADD, element);
}
}
/**
* Inserts a {@link DataElement} at the specified index.
* After the element has been successfully inserted
* all {@link DataListener DataListeners} will be informed.
* @param index Index at which <tt>element</tt> will be inserted.
* All elements with an index &gt;= <tt>index</tt> will be shifted.
* @param element DataElement to be added.
* @throws IllegalArgumentException if <tt>element</tt> is not of the correct
* type which will be checked by the method {@link #isValid}.
*/
public void insertElementAt(int index, DataElement element) {
if (isValid(element)) {
_container.insertElementAt(element, index);
element.setContainer(this);
notifyListeners(DataEvent.createInsertEvent(this, index));
} else {
throwException(INSERT, element);
}
}
/**
* Removes a {@link DataElement} at the specified index.
* After the element has been successfully removed
* all {@link DataListener DataListeners} will be informed.
* @param index Index of the element which will be removed.
* All elements with an index &gt; <tt>index</tt> will be shifted.
*/
public void removeElementAt(int index) {
DataElement element = (DataElement) _container.elementAt(index);
element.setContainer(null);
_container.removeElementAt(index);
notifyListeners(DataEvent.createRemoveEvent(this, index, element));
}
/**
* Replaces the {@link DataElement} at the specified index.
* After the element has been successfully replaced
* all {@link DataListener DataListeners} will be informed.
* @param index Index of the element which will be replaced by
* <tt>element</tt>.
* @param element The new <tt>DataElement</tt>.
* @throws IllegalArgumentException if <tt>element</tt> is not of the correct
* type which will be checked by the method {@link #isValid}.
*/
public void replaceElementAt(int index, DataElement element) {
if (isValid(element)) {
DataElement oldElement = (DataElement) _container.elementAt(index);
oldElement.setContainer(null);
_container.setElementAt(element, index);
element.setContainer(this);
notifyListeners(DataEvent.createReplaceEvent(this, index, oldElement));
} else {
throwException(REPLACE, element);
}
}
private void throwException(String operation, DataElement element) {
throw new IllegalArgumentException(MessageFormat.format(TEMPLATE,
new Object[] {operation, element, this.getClass().getName()}));
}
/**
* Returns <tt>true</tt> if the specified {@link DataElement} has the
* correct type. Concrete subclasses have to implement this method.
* @param element <tt>DataElement</tt> to be checked.
*/
protected abstract boolean isValid(DataElement element);
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.data;
import jcckit.util.ConfigParameters;
/**
* A curve is a {@link DataContainer} of {@link DataPoint DataPoints}.
*
* @author Franz-Josef Elmer
*/
public class DataCurve extends DataContainer implements DataElement {
/** Config parameter key. */
public static final String X_KEY = "x",
Y_KEY = "y",
TITLE_KEY = "title";
private final String _title;
private DataContainer _container;
/** Creates an empty instance with the specified title. */
public DataCurve(String title) {
_title = title;
}
/**
* Creates an instance from the specified config parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>title = </tt><i>empty string</i></td>
* <td><tt>String</tt></td><td>no</td>
* <td>Curve title.</td></tr>
* <tr><td><tt>x</tt></td><td><tt>double[]</tt></td><td>yes</td>
* <td>x-coordinates of the curve points.</td></tr>
* <tr><td><tt>y</tt></td><td><tt>double[]</tt></td><td>yes</td>
* <td>y-coordinates of the curve points.</td></tr>
* </table>
*/
public DataCurve(ConfigParameters config) {
this(config.get(TITLE_KEY, ""));
double[] xPoints = config.getDoubleArray(X_KEY);
double[] yPoints = config.getDoubleArray(Y_KEY);
int n = Math.min(xPoints.length, yPoints.length);
for (int i = 0; i < n; i++) {
addElement(new DataPoint(xPoints[i], yPoints[i]));
}
}
/**
* Returns the {@link DataPlot} containing this curve.
*/
public DataContainer getContainer() {
return _container;
}
/**
* Sets the {@link DataPlot} where this is a curve of.
*/
public void setContainer(DataContainer container) {
_container = container;
}
/** Returns the title of this curve. */
public String getTitle() {
return _title;
}
/**
* Returns <tt>true</tt> if <tt>element</tt> is an instance of
* {@link DataPoint}.
*/
protected boolean isValid(DataElement element) {
return element instanceof DataPoint;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.data;
/**
* Interface for all kinds of data elements.
*
* @author Franz-Josef Elmer
*/
public interface DataElement {
/**
* Returns the container containing this element.
* @return <tt>null</tt> if this element is not an element of a container.
*/
public DataContainer getContainer();
/**
* Sets the container which should contain this element.
* This method should <b>not</b> used outside {@link DataContainer}..
* @param container Container which should contains this element. Cann be
* <tt>null</tt> if this element does not belong to a container.
*/
public void setContainer(DataContainer container);
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.data;
/**
* Event to be sent to a {@link DataListener}.
*
* @author Franz-Josef Elmer
*/
public class DataEvent {
private final DataContainer _container;
private final DataEventType _type;
private final int _index;
private final DataElement _deletedElement;
/**
* Creates an instance for the specified parameters.
* @param container The container which has been changed.
* @param type Type of change.
* @param index Index of the element which has been added, inserted,
* replaced, or removed.
* @param deletedElement Element which has been replaced or removed.
*/
private DataEvent(DataContainer container, DataEventType type, int index,
DataElement deletedElement) {
_container = container;
_type = type;
_index = index;
_deletedElement = deletedElement;
}
/**
* Creates an event of type {@link DataEventType#ELEMENT_ADDED} for the
* specified container.
* @param container Container where an element has been added.
* @return <tt>ELEMENT_ADDED</tt> event.
*/
public static final DataEvent createAddEvent(DataContainer container) {
return new DataEvent(container, DataEventType.ELEMENT_ADDED,
container.getNumberOfElements() - 1, null);
}
/**
* Creates an event of type {@link DataEventType#ELEMENT_INSERTED} for the
* specified container.
* @param container Container where an element has been inserted.
* @param index Index at which an element has been inserted.
* @return <tt>ELEMENT_INSERTED</tt> event.
*/
public static final DataEvent createInsertEvent(DataContainer container,
int index) {
return new DataEvent(container, DataEventType.ELEMENT_INSERTED, index,
null);
}
/**
* Creates an event of type {@link DataEventType#ELEMENT_REPLACED} for the
* specified container.
* @param container Container where an element has been replaced.
* @param index Index of the replaced element.
* @param replacedElement The previous element at <tt>index</tt>.
* @return <tt>ELEMENT_REPLACED</tt> event.
*/
public static final DataEvent createReplaceEvent(DataContainer container,
int index, DataElement replacedElement) {
return new DataEvent(container, DataEventType.ELEMENT_REPLACED, index,
replacedElement);
}
/**
* Creates an event of type {@link DataEventType#ELEMENT_REMOVED} for the
* specified container.
* @param container Container where an element has been removed.
* @param index Index of the removed element.
* @param removedElement The previous element at <tt>index</tt>.
* @return <tt>ELEMENT_REMOVED</tt> event.
*/
public static final DataEvent createRemoveEvent(DataContainer container,
int index, DataElement removedElement) {
return new DataEvent(container, DataEventType.ELEMENT_REMOVED, index,
removedElement);
}
/** Returns the container. */
public DataContainer getContainer() {
return _container;
}
/**
* Returns the event type. Will be one of the constants
* {@link DataEventType#ELEMENT_ADDED},
* {@link DataEventType#ELEMENT_INSERTED},
* {@link DataEventType#ELEMENT_REMOVED}, or
* {@link DataEventType#ELEMENT_REPLACED}.
*/
public DataEventType getType() {
return _type;
}
/** Returns the index. */
public int getIndex() {
return _index;
}
/**
* Returns the deleted element.
* @return <tt>null</tt> if either an element has been added or inserted.
*/
public DataElement getDeletedElement() {
return _deletedElement;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.data;
/**
* Type of a {@link DataEvent}. Implements the typesafe enumeration pattern.
*
* @author Franz-Josef Elmer
*/
public class DataEventType {
private DataEventType() {}
/** Event type. */
public static final DataEventType ELEMENT_ADDED = new DataEventType(),
ELEMENT_INSERTED = new DataEventType(),
ELEMENT_REPLACED = new DataEventType(),
ELEMENT_REMOVED = new DataEventType();
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.data;
/**
* An interface all observers of {@link DataEvent DataEvents}
* have to implement.
*
* @author Franz-Josef Elmer
*/
public interface DataListener {
/**
* Sends the specified data event to this object.
* @param event Data event informing where and what happened.
*/
public void dataChanged(DataEvent event);
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.data;
import jcckit.util.ConfigParameters;
import java.util.StringTokenizer;
/**
* A plot is a {@link DataContainer} of {@link DataCurve DataCurves}.
*
* @author Franz-Josef Elmer
*/
public class DataPlot extends DataContainer {
/** Config parameter key. */
public static final String CURVES_KEY = "curves",
DATA_KEY = "data";
/** Creates an empty instance. */
public DataPlot() {}
/**
* Creates an instance from the specified config parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>curves</tt></td><td><tt>String[]</tt></td><td>yes</td>
* <td>List of keys denoting data curves. Each key refers to
* config parameters used in the
* <a href="DataCurve.html#DataCurve(jcckit.util.ConfigParameters)">
* constructor</a> of {@link DataCurve}.</td></tr>
* </table>
*/
public DataPlot(ConfigParameters config) {
StringTokenizer tokenizer = new StringTokenizer(config.get(CURVES_KEY));
while (tokenizer.hasMoreTokens()) {
addElement(new DataCurve(config.getNode(tokenizer.nextToken())));
}
}
/**
* Convenient method to create a <tt>DataPlot</tt> based on the specified
* config parameters. It is a short-cut of
* <tt>new DataPlot(config.getNode("data"))</tt>.
*/
public static DataPlot create(ConfigParameters config) {
return new DataPlot(config.getNode(DATA_KEY));
}
/**
* Returns <tt>true</tt> if <tt>element</tt> is an instance of
* {@link DataCurve}.
*/
protected boolean isValid(DataElement element) {
return element instanceof DataCurve;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.data;
import jcckit.util.Point;
/**
* Immutable two-dimensional point in data coordinates.
*
* @author Franz-Josef Elmer
*/
public class DataPoint extends Point implements DataElement {
public DataPoint(double x, double y) {
super(x, y);
}
/** Returns always <tt>null</tt>. */
public DataContainer getContainer() {
return null;
}
/** Does nothing. */
public void setContainer(DataContainer container) {}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import jcckit.util.ConfigParameters;
import jcckit.util.FactoryException;
/**
* Anchor of a graphical element. There exist only the three
* instances {@link #LEFT_BOTTOM}, {@link #CENTER}, and
* {@link #RIGHT_TOP}.
* <p>
* The anchor factor can be used in a position formular. Its value
* for the three instances reads:
* <p>
* <center>
* <table border=1 cellpadding=5>
* <tr><th>Instance</th><th>Factor</th></tr>
* <tr><td><tt>LEFT_BOTTOM</tt></td><td>0</td></tr>
* <tr><td><tt>CENTER</tt></td><td>1</td></tr>
* <tr><td><tt>RIGHT_TOP</tt></td><td>2</td></tr>
* </table>
* </center>
*
* @author Franz-Josef Elmer
*/
public class Anchor {
/** Anchor constant. */
public static final Anchor LEFT_BOTTOM = new Anchor(0),
CENTER = new Anchor(1),
RIGHT_TOP = new Anchor(2);
private static final String LEFT_VALUE = "left",
RIGHT_VALUE = "right",
CENTER_VALUE = "center",
TOP_VALUE = "top",
BOTTOM_VALUE = "bottom";
/**
* Returns form the specified configuration parameters the
* horizontal anchor defined by the specified key or the
* specified default value.
* @param config Configuration parameters.
* @param key The key of the anchor. <tt>null</tt> is not allowed.
* @param defaultValue The default value.
* @return one of the three instances of <tt>Anchor</tt>.
* @throws FactoryException if the value of <tt>key</tt> is
* neither <tt>left</tt>, <tt>center</tt>,
* nor <tt>right</tt>.
* Note, that {@link FactoryException#getClassName()}
* returns the invalid value.
*/
public static Anchor getHorizontalAnchor(ConfigParameters config, String key,
Anchor defaultValue) {
Anchor result = defaultValue;
String anchor = config.get(key, null);
if (anchor != null) {
if (anchor.equals(LEFT_VALUE)) {
result = Anchor.LEFT_BOTTOM;
} else if (anchor.equals(CENTER_VALUE)) {
result = Anchor.CENTER;
} else if (anchor.equals(RIGHT_VALUE)) {
result = Anchor.RIGHT_TOP;
} else {
throw new FactoryException(config, key, "Invalid horizontal anchor.");
}
}
return result;
}
/**
* Returns form the specified configuration parameters the
* vertical anchor defined by the specified key or the
* specified default value.
* @param config Configuration parameters.
* @param key The key of the anchor. <tt>null</tt> is not allowed.
* @param defaultValue The default value.
* @return one of the three instances of <tt>Anchor</tt>.
* @throws FactoryException if the value of <tt>key</tt> is
* neither <tt>top</tt>, <tt>center</tt>,
* nor <tt>bottom</tt>.
* Note, that {@link FactoryException#getClassName()}
* returns the invalid value.
*/
public static Anchor getVerticalAnchor(ConfigParameters config, String key,
Anchor defaultValue) {
Anchor result = defaultValue;
String anchor = config.get(key, null);
if (anchor != null) {
if (anchor.equals(BOTTOM_VALUE)) {
result = Anchor.LEFT_BOTTOM;
} else if (anchor.equals(CENTER_VALUE)) {
result = Anchor.CENTER;
} else if (anchor.equals(TOP_VALUE)) {
result = Anchor.RIGHT_TOP;
} else {
throw new FactoryException(config, key, "Invalid vertcal anchor.");
}
}
return result;
}
private final int _factor;
private Anchor(int factor) {
_factor = factor;
}
/** Returns the factor. */
public int getFactor() {
return _factor;
}
}

View File

@ -0,0 +1,202 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import jcckit.util.ConfigParameters;
import java.awt.Color;
/**
* The basic attributes of any {@link BasicGraphicalElement}. This is an
* extension of {@link ShapeAttributes} implementing {@link TextAttributes}.
*
* @author Franz-Josef Elmer
*/
public class BasicGraphicAttributes extends ShapeAttributes
implements TextAttributes {
/** Configuration parameter key. */
public static final String TEXT_COLOR_KEY = "textColor",
FONT_NAME_KEY = "fontName",
FONT_STYLE_KEY = "fontStyle",
FONT_SIZE_KEY = "fontSize",
HORIZONTAL_ANCHOR_KEY = "horizontalAnchor",
VERTICAL_ANCHOR_KEY = "verticalAnchor",
ORIENTATION_ANGLE_KEY = "orientationAngle";
private final Color _textColor;
private final String _fontName;
private final FontStyle _fontStyle;
private final double _fontSize;
private final double _orientationAngle;
private final Anchor _horizontalAnchor;
private final Anchor _verticalAnchor;
/**
* Creates a new instance based on the specified configuration
* parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>textColor = </tt><i>default foreground color of the
* renderer</i></td><td><tt>Color</tt></td><td>no</td>
* <td>The text color.</td></tr>
* <tr><td><tt>fontName = </tt><i>default font name of the
* renderer</i></td><td><tt>String</tt></td><td>no</td>
* <td>The name of the text font. The standard Java font name
* "Serif", "SansSerif", and "Monospaced" can be used.
* Other font names depend on the actual {@link Renderer}
* rendering the corresponding {@link BasicGraphicalElement}.
* </td></tr>
* <tr><td><tt>fontStyle = normal</tt></td><td><tt>String</tt>
* </td><td>no</td>
* <td>The font style. Possible values are:
* <ul><li><tt>normal</tt><li><tt>bold</tt><li><tt>italic</tt>
* <li><tt>bold italic</tt></ul>
* </td></tr>
* <tr><td><tt>fontSize = </tt><i>default font size of the
* renderer</i></td><td><tt>double</tt></td><td>no</td>
* <td>The font size in units of the device-independent
* coordinates.</td></tr>
* <tr><td><tt>orientationAngle = 0</tt></td><td><tt>double</tt></td>
* <td>no</td>
* <td>The orientation angle of the text (in degree).
* Zero means normal orientation whereas a positive value means
* a rotation in counter-clockweise direction.</td></tr>
* <tr><td><tt>horizontalAnchor = left</tt></td><td><tt>String</tt>
* </td><td>no</td>
* <td>Anchor for horizontal text position. Possible values are
* <tt>left</tt>, <tt>center</tt>, and <tt>right</tt>.</td></tr>
* <tr><td><tt>verticalAnchor = center</tt></td><td><tt>String</tt>
* </td><td>no</td>
* <td>Anchor for vertical text position. Possible values are
* <tt>top</tt>, <tt>center</tt>, and <tt>bottom</tt>.</td></tr>
* </table>
* Additional configuration parameters are explained in the
* {@link ShapeAttributes#ShapeAttributes constructor}
* of the superclass {@link ShapeAttributes}.
*/
public BasicGraphicAttributes(ConfigParameters config) {
super(config);
_textColor = config.getColor(TEXT_COLOR_KEY, null);
_fontName = config.get(FONT_NAME_KEY, null);
_fontStyle = FontStyle.getFontStyle(config, FONT_STYLE_KEY,
FontStyle.NORMAL);
_fontSize = config.getDouble(FONT_SIZE_KEY, 0);
_orientationAngle = config.getDouble(ORIENTATION_ANGLE_KEY, 0);
_horizontalAnchor = Anchor.getHorizontalAnchor(config,
HORIZONTAL_ANCHOR_KEY, Anchor.LEFT_BOTTOM);
_verticalAnchor = Anchor.getVerticalAnchor(config,
VERTICAL_ANCHOR_KEY, Anchor.CENTER);
}
/**
* Creates a new instance.
* @param fillColor The fill color. May be <tt>null</tt>.
* @param lineColor The line color. May be <tt>null</tt>.
* @param lineThickness Thickness of the line.
* Negative numbers will be trimmed to zero.
* @param linePattern Line pattern. May be <tt>null</tt>.
* @param textColor The text color. May be <tt>null</tt>.
* @param fontName The font name. May be <tt>null</tt>.
* @param fontStyle The font style. May be <tt>null</tt>.
* @param fontSize The font size in units of the device-independent
* coordinates. May be <tt>null</tt>.
* @param orientationAngle Orientation angle of the text.
* @param horizontalAnchor Horizontal text anchor.
* @param verticalAnchor Vertical text anchor.
*/
public BasicGraphicAttributes(Color fillColor, Color lineColor,
double lineThickness,
double[] linePattern, Color textColor,
String fontName, FontStyle fontStyle,
double fontSize, double orientationAngle,
Anchor horizontalAnchor,
Anchor verticalAnchor) {
super(fillColor, lineColor, lineThickness, linePattern);
_textColor = textColor;
_fontName = fontName;
_fontStyle = fontStyle;
_fontSize = fontSize;
_orientationAngle = orientationAngle;
_horizontalAnchor = horizontalAnchor;
_verticalAnchor = verticalAnchor;
}
/**
* Returns the text color.
* @return <tt>null</tt> means default color of the renderer.
*/
public Color getTextColor() {
return _textColor;
}
/**
* Returns the font name.
* @return <tt>null</tt> means default font name of the renderer.
*/
public String getFontName() {
return _fontName;
}
/**
* Returns the font style.
* @return <tt>null</tt> means default font style of the renderer.
*/
public FontStyle getFontStyle() {
return _fontStyle;
}
/**
* Returns the font size in units of the device-independent coordinates.
*/
public double getFontSize() {
return _fontSize;
}
/**
* Returns the orientation angle in degree. Zero means
* normal text orientation. Any positive angle means a
* counter-clockwise rotation of the text.
*/
public double getOrientationAngle() {
return _orientationAngle;
}
/**
* Returns the anchor for horizontal position of the text.
* Note, that the anchor is related to the text <em>before</em>
* it is rotated by the orientation angle.
* @return one of the three instances of <tt>Anchor</tt>.
*/
public Anchor getHorizontalAnchor() {
return _horizontalAnchor;
}
/**
* Returns the anchor for vertical position of the text.
* Note, that the anchor is related to the text <em>before</em>
* it is rotated by the orientation angle.
* @return one of the three instances of <tt>Anchor</tt>.
*/
public Anchor getVerticalAnchor() {
return _verticalAnchor;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Abstract superclass of all basic {@link GraphicalElement
* GraphicalElements}. Concrete subclasses have to implement
* the method {@link GraphicalElement#renderWith}.
*
* @author Franz-Josef Elmer
*/
public abstract class BasicGraphicalElement implements GraphicalElement {
private final GraphicAttributes _attributes;
/**
* Creates an instance with the specified drawing attributes.
* Note, that a {@link Renderer} should use default attributes
* in the case no attributes are defined.
* @param attributes Drawing attributes or <tt>null</tt> if undefined.
*/
public BasicGraphicalElement(GraphicAttributes attributes) {
_attributes = attributes;
}
/**
* Returns the drawing attributes.
* @return <tt>null</tt> if undefined.
*/
public GraphicAttributes getGraphicAttributes() {
return _attributes;
}
/**
* Returns whether this basic graphical element has a closed shape
* or not. By default always <tt>true</tt>. Subclasses may override
* this behaviour.
* @return <tt>true</tt> if the shape is closed.
*/
public boolean isClosed() {
return true;
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Immutable class of a rectangular clipping area.
*
* @author Franz-Josef Elmer
*/
public class ClippingRectangle implements ClippingShape {
private final double _minX, _minY, _maxX, _maxY;
/**
* Creates an instance for the specified coordinates of
* two opposite corner points.
*/
public ClippingRectangle(double x1, double y1, double x2, double y2) {
_minX = Math.min(x1, x2);
_minY = Math.min(y1, y2);
_maxX = Math.max(x1, x2);
_maxY = Math.max(y1, y2);
}
/**
* Returns <tt>true</tt> if the specified point is inside this
* rectangle.
*/
public boolean isInside(GraphPoint point) {
double x = point.getX();
double y = point.getY();
return _minX <= x && x <= _maxX && _minY <= y && y <= _maxY;
}
/** Returns the minimum x value. */
public double getMinX() {
return _minX;
}
/** Returns the maximum x value. */
public double getMaxX() {
return _maxX;
}
/** Returns the minimum y value. */
public double getMinY() {
return _minY;
}
/** Returns the maximum y value. */
public double getMaxY() {
return _maxY;
}
/** Returns this instance. */
public ClippingRectangle getBoundingBox() {
return this;
}
/** Returns a {@link Rectangle}. */
public BasicGraphicalElement getGraphicalElement() {
return new Rectangle(new GraphPoint(0.5 * (_minX + _maxX),
0.5 * (_minY + _maxY)),
_maxX - _minX, _maxY - _minY, null);
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Defining a clipping shape applied to all {@link GraphicalElement
* GraphicalElements} of a {@link GraphicalComposite}.
*
* @author Franz-Josef Elmer
*/
public interface ClippingShape {
/**
* Returns <tt>true</tt> if the specified point is inside this
* clipping shape.
*/
public boolean isInside(GraphPoint point);
/**
* Returns the bounding box of this clipping shape.
* This method will be used by renderers who supports only
* rectangular clipping shapes.
*/
public ClippingRectangle getBoundingBox();
/**
* Returns a basic graphical element (such as {@link Rectangle}
* or {@link Polygon}) which may be used by renderers to
* define the clipping shape for the output device.
*/
public BasicGraphicalElement getGraphicalElement();
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import java.awt.Color;
/**
* Interface for fill attributes.
*
* @author Franz-Josef Elmer
*/
public interface FillAttributes extends GraphicAttributes {
/**
* Returns the fill color.
* @return <tt>null</tt> means no filling.
*/
public Color getFillColor();
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import jcckit.util.ConfigParameters;
import jcckit.util.FactoryException;
import java.util.Hashtable;
/**
* Font style constants.
* This class is based on the typesafe enumeration pattern.
*
* @author Franz-Josef Elmer
*/
public class FontStyle {
private static final Hashtable REPOSITORY = new Hashtable();
static final String NORMAL_TXT = "normal",
BOLD_TXT = "bold",
ITALIC_TXT = "italic",
BOLD_ITALIC_TXT = "bold italic";
/** Font style constant. */
public static final FontStyle NORMAL = new FontStyle(NORMAL_TXT),
BOLD = new FontStyle(BOLD_TXT),
ITALIC = new FontStyle(ITALIC_TXT),
BOLD_ITALIC = new FontStyle(BOLD_ITALIC_TXT);
private final String _description;
/** Non-public constructor to control the number of instances. */
private FontStyle(String description) {
_description = description;
REPOSITORY.put(description, this);
}
/**
* Returns from the specified configuration parameters the font style
* defined by the specified key or the specified default value.
* @param config Configuration parameters.
* @param key The key of the font style.
* @param defaultValue The default value.
* @return one of the four instances of <tt>FontStyle</tt>.
* @throws FactoryException if the value of the key-value pair denoted
* by <tt>key</tt> is neither <tt>normal</tt>, <tt>bold</tt>,
* <tt>italic</tt>, nor <tt>bold italic</tt>,
* Note, that {@link FactoryException#getClassName()}
* returns the invalid value.
*/
public static FontStyle getFontStyle(ConfigParameters config, String key,
FontStyle defaultValue) {
FontStyle result = defaultValue;
String value = config.get(key, null);
if (value != null) {
result = (FontStyle) REPOSITORY.get(value);
if (result == null) {
throw new FactoryException(config, key, "Invalid font style.");
}
}
return result;
}
/** Returns a human readable description for pretty printing. */
public String toString() {
return _description;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import jcckit.util.Point;
/**
* Immutable class of a two-dimensional point in the device-independent
* coordinate system.
*
* @author Franz-Josef Elmer
*/
public class GraphPoint extends Point {
/**
* Creates an instance for the specified vector.
* If <tt>vector</tt> is <tt>null</tt> or not long enough the
* default value 0 will be used instead.
*/
public GraphPoint(double[] vector) {
super(vector);
}
/** Creates an instance for the specified coordinates. */
public GraphPoint(double x, double y) {
super(x, y);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Marker interface for all types of graphic attributes
* of a {@link BasicGraphicalElement}. Graphic attributes are only
* hints for {@link Renderer Renderers} how to render a
* <tt>BasicGraphicalElement</tt>. Whether they are used and how
* they are interpreted depends on the concrete <tt>Renderer</tt>.
* <p>
* This is only a marker interface. There are several subinterfaces
* specifying various attributes grouped by the type of element to
* be rendered.
*
* @author Franz-Josef Elmer
*/
public interface GraphicAttributes {
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import java.util.Vector;
/**
* Container for {@link GraphicalElement GraphicalElements}.
*
* @author Franz-Josef Elmer
*/
public class GraphicalComposite implements GraphicalElement {
private final Vector _elements = new Vector();
private final ClippingShape _clippingShape;
/**
* Creates an instance with the specified clipping shape.
* @param clippingShape Clipping shape or <tt>null</tt> if no clipping.
*/
public GraphicalComposite(ClippingShape clippingShape) {
_clippingShape = clippingShape;
}
/**
* Returns the clipping shape.
* @return <tt>null</tt> if no clipping should be applied.
*/
public ClippingShape getClippingShape() {
return _clippingShape;
}
/**
* Adds the specified element at the end of the list of elements.
* @param element Element to be added. <tt>null</tt> is not allowed.
* @throws NullPointerException if <tt>element == null</tt>
*/
public void addElement(GraphicalElement element) {
if (element == null) {
throwNullPointerException();
} else {
_elements.addElement(element);
}
}
/** Remove all elements. */
public void removeAllElements() {
_elements.removeAllElements();
}
/**
* Replaces the specified element at the specified index of
* the list of elements.
* @param element New element. <tt>null</tt> is not allowed.
* @throws NullPointerException if <tt>element == null</tt>
*/
public void replaceElementAt(int index, GraphicalElement element) {
if (element == null) {
throwNullPointerException();
} else {
_elements.setElementAt(element, index);
}
}
private void throwNullPointerException() {
throw new NullPointerException(
"A null as an GraphicalElement is not allowed");
}
/**
* Renders all {@link GraphicalElement GraphicalElements} in the sequence
* they have been added.
* @param renderer Renderer which implements all renderer interfaces
* necessary to render the child elements.
* @throws IllegalArgumentException if <tt>renderer</tt> is not
* an instance of <tt>GraphicalCompositeRenderer</tt>.
*/
public void renderWith(Renderer renderer) {
if (renderer instanceof GraphicalCompositeRenderer) {
GraphicalCompositeRenderer r = (GraphicalCompositeRenderer) renderer;
r.startRendering(this);
for (int i = 0, n = _elements.size(); i < n; i++) {
((GraphicalElement) _elements.elementAt(i)).renderWith(r);
}
r.finishRendering(this);
} else {
throw new IllegalArgumentException(renderer
+ " does not implements GraphicalCompositeRenderer.");
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Interface of all {@link Renderer Renderers} who render a
* {@link GraphicalComposite}. Note, that a
* <tt>GraphicalCompositeRenderer</tt> does <em>not</em>
* render the element of a <tt>GraphicalComposite</tt>
*
* @author Franz-Josef Elmer
*/
public interface GraphicalCompositeRenderer extends Renderer {
/**
* Starts rendering of the specified composite before its
* elements are rendererd. Implementations of this method
* usually obtain the {@link ClippingShape} from
* <tt>composite</tt>.
*/
public void startRendering(GraphicalComposite composite);
/** Finishes rendering of the specified composite. */
public void finishRendering(GraphicalComposite composite);
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Interface all graphical elements have to implement.
* Together with the marker interface {@link Renderer} it
* realizes the Anticyclic Visitor Pattern, a variant of the
* GoF Visitor Pattern. This allows not only to extend JCCKit with
* new renderers but also with new types of <tt>GraphicalElements</tt>
* without touching existing code.
*
* @author Franz-Josef Elmer
*/
public interface GraphicalElement {
/**
* Renders this element according to the type of renderer.
* Concrete <tt>GraphicalElements</tt> who are not instances of
* {@link GraphicalComposite} dynamically cast <tt>renderer</tt>.
* If it does not implement the type of renderer specific for
* the concrete <tt>GraphicalElement</tt> it should throw an
* <tt>IllegalArgumentException</tt>.
*/
public abstract void renderWith(Renderer renderer);
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import java.awt.Color;
/**
* Interface for line attributes.
*
* @author Franz-Josef Elmer
*/
public interface LineAttributes extends GraphicAttributes {
/**
* Returns the line color.
* @return <tt>null</tt> means default color of the renderer.
*/
public Color getLineColor();
/**
* Returns the line tickness. 0 means that the line thickness is
* chosen as thin as possible.
* Implementations have to guarantee that the returned value is
* never negative.
*/
public double getLineThickness();
/**
* Returns the line pattern. This is a sequence of length where the
* pen is down or up. The first element is the length where the
* pen is down. The next element is the length where the pen is up.
* The pattern is cyclically repeated.
* @return <tt>null</tt> means solid line.
*/
public double[] getLinePattern();
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* An oval (i.e.&nbsp;an ellipse).
*
* @author Franz-Josef Elmer
*/
public class Oval extends Rectangle {
/**
* Creates a new instance.
* @param center The position of the center of this element.
* @param width The width.
* @param height The height.
* @param attributes Drawing attributes. Can be <tt>null</tt>.
*/
public Oval(GraphPoint center, double width, double height,
GraphicAttributes attributes) {
super(center, width, height, attributes);
}
/**
* Renders this oval with the specified {@link Renderer}.
* @param renderer An instance of {@link OvalRenderer}.
* @throws IllegalArgumentException if <tt>renderer</tt> is not
* an instance of <tt>OvalRenderer</tt>.
*/
public void renderWith(Renderer renderer) {
if (renderer instanceof OvalRenderer) {
((OvalRenderer) renderer).render(this);
} else {
throw new IllegalArgumentException(renderer
+ " does not implements OvalRenderer.");
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Interface of all {@link Renderer Renderers} who render a
* {@link Oval}.
*
* @author Franz-Josef Elmer
*/
public interface OvalRenderer extends Renderer {
/** Renders the specified oval. */
public void render(Oval oval);
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import java.util.Vector;
/**
* A polygon or polyline.
*
* @author Franz-Josef Elmer
*/
public class Polygon extends BasicGraphicalElement {
private final Vector _points = new Vector();
private final boolean _closed;
/**
* Creates an instance of the specified graphic attributes.
* @param closed <tt>true</tt> if this polygon is closed.
*/
public Polygon(GraphicAttributes attributes, boolean closed) {
super(attributes);
_closed = closed;
}
/** Returns <tt>true</tt> if this polygon is closed. */
public boolean isClosed() {
return _closed;
}
/** Returns the number points. */
public int getNumberOfPoints() {
return _points.size();
}
/** Returns the point for the specified index. */
public GraphPoint getPoint(int index) {
return (GraphPoint) _points.elementAt(index);
}
/** Adds a new point to the end of the list of points. */
public void addPoint(GraphPoint point) {
_points.addElement(point);
}
/** Removes all points. */
public void removeAllPoints() {
_points.removeAllElements();
}
/** Replaces the point at the specified index by a new one. */
public void replacePointAt(int index, GraphPoint point) {
_points.setElementAt(point, index);
}
/**
* Renders this line with the specified {@link Renderer}.
* @param renderer An instance of {@link PolygonRenderer}.
* @throws IllegalArgumentException if <tt>renderer</tt> is not
* an instance of <tt>PolygonRenderer</tt>.
*/
public void renderWith(Renderer renderer) {
if (renderer instanceof PolygonRenderer) {
((PolygonRenderer) renderer).render(this);
} else {
throw new IllegalArgumentException(renderer
+ " does not implements PolygonRenderer.");
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Interface of all {@link Renderer Renderers} who render an
* instance of {@link Polygon}.
*
* @author Franz-Josef Elmer
*/
public interface PolygonRenderer extends Renderer {
/** Renders the specified <tt>Polygon</tt> instance. */
public void render(Polygon polygon);
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* A rectangle.
*
* @author Franz-Josef Elmer
*/
public class Rectangle extends BasicGraphicalElement {
private final GraphPoint _center;
private final double _width;
private final double _height;
/**
* Creates a new instance.
* @param center The position of the center of this element.
* @param width The width.
* @param height The height.
* @param attributes Drawing attributes. Can be <tt>null</tt>.
*/
public Rectangle(GraphPoint center, double width, double height,
GraphicAttributes attributes) {
super(attributes);
_center = center;
_width = width;
_height = height;
}
/** Returns the center of this element. */
public GraphPoint getCenter() {
return _center;
}
/** Returns the width of this element. */
public double getWidth() {
return _width;
}
/** Returns the height of this element. */
public double getHeight() {
return _height;
}
/**
* Renders this rectangle with the specified {@link Renderer}.
* @param renderer An instance of {@link RectangleRenderer}.
* @throws IllegalArgumentException if <tt>renderer</tt> is not
* an instance of <tt>RectangleRenderer</tt>.
*/
public void renderWith(Renderer renderer) {
if (renderer instanceof RectangleRenderer) {
((RectangleRenderer) renderer).render(this);
} else {
throw new IllegalArgumentException(renderer
+ " does not implements RectangleRenderer.");
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Interface of all {@link Renderer Renderers} who render a
* {@link Rectangle}.
*
* @author Franz-Josef Elmer
*/
public interface RectangleRenderer extends Renderer {
/** Renders the specified rectangle. */
public void render(Rectangle rectangle);
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Marker interface. Each subclass is an interface for a specific
* type of {@link GraphicalElement}.
*
* @author Franz-Josef Elmer
*/
public interface Renderer {}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import jcckit.util.ConfigParameters;
import java.awt.Color;
/**
* Basic attributes for shapes.
*
* @author Franz-Josef Elmer
*/
public class ShapeAttributes implements LineAttributes, FillAttributes {
/** Configuration parameter key. */
public static final String FILL_COLOR_KEY = "fillColor",
LINE_COLOR_KEY = "lineColor",
LINE_THICKNESS_KEY = "lineThickness",
LINE_PATTERN_KEY = "linePattern";
private final Color _fillColor;
private final Color _lineColor;
private final double _lineThickness;
private final double[] _linePattern;
/**
* Creates a new instance based on the specified configuration
* parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>fillColor = <i>no filling</i></tt></td><td><tt>Color</tt></td>
* <td>no</td><td>The fill color of the shape.</td></tr>
* <tr><td><tt>lineColor = <i>no line<i></tt></td><td><tt>Color</tt></td>
* <td>no</td><td>The color of a line, a polygon, or the border of a shape.</td></tr>
* <tr><td><tt>lineThickness = 0</tt></td><td><tt>double</tt></td>
* <td>no</td>
* <td>The thickness of a line. A thickness of zero means that
* the renderer will draw the thinest line possible.</td></tr>
* <tr><td><tt>linePattern = </tt><i>solid line</i></td>
* <td><tt>double[]</tt></td><td>no</td>
* <td>A sequence of lengths where the pen is alternatively
* down or up. For example, <tt>0.1 0.1</tt> will lead to a dashed
* line whereas <tt>0.02 0.02</tt> is the pattern of a dotted
* line and <tt>0.02 0.02 0.1 0.02</tt> of a dashed-dotted
* line.</td></tr>
* </table>
*/
public ShapeAttributes(ConfigParameters config) {
this(config.getColor(FILL_COLOR_KEY, null),
config.getColor(LINE_COLOR_KEY, null),
config.getDouble(LINE_THICKNESS_KEY, 0),
config.getDoubleArray(LINE_PATTERN_KEY, null));
}
/**
* Creates a new instance.
* @param fillColor The fill color. May be <tt>null</tt>.
* @param lineColor The line color. May be <tt>null</tt>.
* @param lineThickness Thickness of the line.
* Negative numbers will be trimmed to zero.
* @param linePattern Line pattern. May be <tt>null</tt>.
*/
public ShapeAttributes(Color fillColor, Color lineColor,
double lineThickness, double[] linePattern) {
_fillColor = fillColor;
_lineColor = lineColor;
_lineThickness = Math.max(0, lineThickness);
_linePattern = linePattern;
}
public Color getFillColor() {
return _fillColor;
}
public Color getLineColor() {
return _lineColor;
}
public double getLineThickness() {
return _lineThickness;
}
public double[] getLinePattern() {
return _linePattern;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* A single line of text.
*
* @author Franz-Josef Elmer
*/
public class Text extends BasicGraphicalElement {
private final GraphPoint _position;
private final String _text;
/**
* Creates an instance with the specified parameters.
* @param position Position of the text.
* @param text Text.
* @param attributes Drawing attributes. Can be <tt>null</tt>.
*/
public Text(GraphPoint position, String text, GraphicAttributes attributes) {
super(attributes);
_position = position;
_text = text;
}
/** Returns the position. */
public GraphPoint getPosition() {
return _position;
}
/** Returns the text string. */
public String getText() {
return _text;
}
/**
* Renders this line with the specified {@link Renderer}.
* @param renderer An instance of {@link TextRenderer}.
* @throws IllegalArgumentException if <tt>renderer</tt> is not
* an instance of <tt>TextRenderer</tt>.
*/
public void renderWith(Renderer renderer) {
if (renderer instanceof TextRenderer) {
((TextRenderer) renderer).render(this);
} else {
throw new IllegalArgumentException(renderer
+ " does not implements TextRenderer.");
}
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
import java.awt.Color;
/**
* Interface for text attributes.
*
* @author Franz-Josef Elmer
*/
public interface TextAttributes extends GraphicAttributes {
/**
* Returns the text color.
* @return <tt>null</tt> means default color of the renderer.
*/
public Color getTextColor();
/**
* Returns the font name.
* @return <tt>null</tt> means default font name of the renderer.
*/
public String getFontName();
/**
* Returns the font style.
* @return <tt>null</tt> means default font style of the renderer.
*/
public FontStyle getFontStyle();
/**
* Returns the font size in units of the device-independent coordinates.
*/
public double getFontSize();
/**
* Returns the orientation angle in degree. Zero means
* normal text orientation. Any positive angle means a
* counter-clockwise rotation of the text.
*/
public double getOrientationAngle();
/**
* Returns the anchor for horizontal position of the text.
* Note, that the anchor is related to the text <em>before</em>
* it is rotated by the orientation angle.
* @return one of the three instances of <tt>Anchor</tt>.
*/
public Anchor getHorizontalAnchor();
/**
* Returns the anchor for vertical position of the text.
* Note, that the anchor is related to the text <em>before</em>
* it is rotated by the orientation angle.
* @return one of the three instances of <tt>Anchor</tt>.
*/
public Anchor getVerticalAnchor();
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.graphic;
/**
* Interface of all {@link Renderer Renderers} who render an
* instance of {@link Text}.
*
* @author Franz-Josef Elmer
*/
public interface TextRenderer extends Renderer {
/** Renders the specified <tt>Text</tt> instance. */
public void render(Text text);
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.GraphPoint;
import jcckit.graphic.GraphicAttributes;
import jcckit.graphic.GraphicalElement;
import jcckit.util.ConfigParameters;
import jcckit.util.Factory;
/**
* Abstract superclass of all {@link SymbolFactory SymbolFactories}.
* Subclasses have to implement {@link #createPlainSymbol createPlainSymbol()}.
*
* @author Franz-Josef Elmer
*/
public abstract class AbstractSymbolFactory implements SymbolFactory {
/** Size of all symbols. */
protected final double _size;
/** Attributes of all symbols. */
protected final GraphicAttributes _attributes;
/**
* Creates an instance from the specified configuration parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>size = </tt>0.01</td>
* <td><tt>double</tt></td><td>no</td>
* <td>Size of the symbol in device-independent units.</td></tr>
* <tr><td><tt>attributes</tt></td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Configuration parameters for the attributes of the symbol.
* <tt>className</tt> has to be a class which is an instance of
* {@link GraphicAttributes}.</td></tr>
* </table>
*/
public AbstractSymbolFactory(ConfigParameters config) {
_size = config.getDouble(SIZE_KEY, DEFAULT_SIZE);
_attributes = (GraphicAttributes) Factory.createOrGet(
config.getNode(ATTRIBUTES_KEY), null);
}
/**
* Creates a symbol.
* Evaluate <tt>hintFromPreviousPoint</tt> if it is a {@link AttributesHint}.
* Calls {@link #createSymbol(GraphPoint, GraphicAttributes, Hint, Hint)}.
* @param point Symbol position.
* @param hintFromPreviousPoint Hint from the previous point.
* @param hintFromPreviousCurve Hint from the previous curve.
*/
public Symbol createSymbol(GraphPoint point, Hint hintFromPreviousPoint,
Hint hintFromPreviousCurve) {
GraphicAttributes attributes = _attributes;
Hint hintForNextPoint = hintFromPreviousPoint;
if (hintFromPreviousPoint instanceof AttributesHint) {
attributes = ((AttributesHint) hintFromPreviousPoint).getAttributes();
hintForNextPoint
= ((AttributesHint) hintFromPreviousPoint).getNextHint();
}
return createSymbol(point, attributes, hintForNextPoint,
hintFromPreviousCurve);
}
/**
* Creates a symbol.
* Uses {@link #createPlainSymbol createPlainSymbol()}.
* @param point Symbol position.
* @param attributes Symbol attributes.
* @param hintForNextPoint Hint for the next point. Will be delivered
* unchanged in the return <tt>Symbol</tt> object.
* @param hintFromPreviousCurve Hint from the previous curve.
* Will be delivered unchanged in the return <tt>Symbol</tt> object.
* Subclasses may override this behavior.
*/
protected Symbol createSymbol(GraphPoint point, GraphicAttributes attributes,
Hint hintForNextPoint,
Hint hintFromPreviousCurve) {
return new Symbol(createPlainSymbol(point, _size, attributes),
hintForNextPoint, hintFromPreviousCurve);
}
/**
* Creates a symbol for the legend at the specified position.
* Uses {@link #createPlainSymbol createPlainSymbol()}
* @param centerPosition Center position of the symbol.
* @param size The size of the symbol. Will be ignored because the value
* given in the constructor will be used.
*/
public GraphicalElement createLegendSymbol(GraphPoint centerPosition,
double size) {
return createPlainSymbol(centerPosition, _size, _attributes);
}
/**
* Creates the graphical element of the plain symbol.
* @param centerPosition Center position of the symbol.
* @param size The size of the symbol.
* @param attributes The attributes of the symbol.
*/
protected abstract GraphicalElement createPlainSymbol(
GraphPoint centerPosition, double size, GraphicAttributes attributes);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.GraphicAttributes;
/**
* A {@link Hint} which wraps a {@link GraphicAttributes} instance.
* In addition the method {@link #getNextHint()} creates a new instance
* with different attributes derivated from the wrapped attributes.
*
* @author Franz-Josef Elmer
*/
public interface AttributesHint extends Hint {
/**
* Returns the hint for the next {@link Symbol} of a {@link Curve}.
* The new hint has a different {@link GraphicAttributes}.
*/
public AttributesHint getNextHint();
/** Returns the attributes value. */
public GraphicAttributes getAttributes();
}

View File

@ -0,0 +1,496 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import java.util.Properties;
import jcckit.graphic.BasicGraphicAttributes;
import jcckit.graphic.GraphPoint;
import jcckit.graphic.LineAttributes;
import jcckit.graphic.ShapeAttributes;
import jcckit.graphic.TextAttributes;
import jcckit.util.ConfigData;
import jcckit.util.ConfigParameters;
import jcckit.util.ConfigParametersBasedConfigData;
import jcckit.util.Factory;
import jcckit.util.Format;
import jcckit.util.PropertiesBasedConfigData;
import jcckit.util.TicLabelFormat;
import jcckit.util.Util;
/**
* Helper class with various parameters defining an axis.
* This helper class is used by {@link CartesianCoordinateSystem}
* to set up a coordinate systems.
* <p>
* This class holds more than a dozen parameters. There are two factory
* methods creating instances for x- and y-axis based on
* {@link ConfigParameters}. They differ in their default parameters for
* those axes.
* <p>
* Note, that there is a direct access of these parameters without getters
* and setters but only for classes in the package <tt>jcckit.plot</tt>.
*
* @author Franz-Josef Elmer
*/
public class AxisParameters {
/** Configuration parameter key. */
public static final String LOG_SCALE_KEY = "logScale",
MINIMUM_KEY = "minimum",
MAXIMUM_KEY = "maximum",
AXIS_LENGTH_KEY = "axisLength",
AXIS_ATTRIBUTES_KEY = "axisAttributes",
AXIS_LABEL_KEY = "axisLabel",
AXIS_LABEL_POSITION_KEY = "axisLabelPosition",
AXIS_LABEL_ATTRIBUTES_KEY = "axisLabelAttributes",
AUTOMATIC_TIC_CALCULATION_KEY
= "automaticTicCalculation",
MINIMUM_TIC_KEY = "minimumTic",
MAXIMUM_TIC_KEY = "maximumTic",
NUMBER_OF_TICS_KEY = "numberOfTics",
TIC_LENGTH_KEY = "ticLength",
TIC_ATTRIBUTES_KEY = "ticAttributes",
TIC_LABEL_FORMAT_KEY = "ticLabelFormat",
TIC_LABEL_POSITION_KEY = "ticLabelPosition",
TIC_LABEL_ATTRIBUTES_KEY = "ticLabelAttributes",
GRID_KEY = "grid",
GRID_ATTRIBUTES_KEY = "gridAttributes";
private static final double LN10 = Math.log(10);
/** If <tt>true</tt> the scale is logarithmic otherwise linear. */
boolean logScale;
/** Minimum data value represented by the axis. */
double minimum;
/** Maximum data value represented by the axis. */
double maximum;
/** Length of the axis in device-independent graphical units. */
double axisLength;
/**
* Line attributes of the axis.
* Can be <tt>null</tt> which means default attributes.
*/
LineAttributes axisAttributes;
boolean automaticTicCalculation;
double minimumTic;
double maximumTic;
int numberOfTics;
/**
* Length of the tics in device-independent graphical units.
* If 0 no tics and tics label will be drawn.
*/
double ticLength;
/**
* Attributes of the tics.
* Can be <tt>null</tt> which means default attributes.
*/
LineAttributes ticAttributes;
/** Tic label formatter. */
TicLabelFormat ticLabelFormat;
/** Position of the tic label relative to the tic. */
GraphPoint ticLabelPosition;
/** Text attributes of the tic labels. */
TextAttributes ticLabelAttributes;
/** If <tt>true</tt> grid lines are drawn. */
boolean grid;
/**
* Attributes of the grid lines.
* Can be <tt>null</tt> which means default attributes.
*/
LineAttributes gridAttributes;
/** Axis label. */
String axisLabel;
/** Position of the axis label relative to the center of the axis. */
GraphPoint axisLabelPosition;
/** Text attributes of the axis label. */
TextAttributes axisLabelAttributes;
/**
* Calculate the tics based on <tt>minimumTic</tt>, <tt>maximumTic</tt>,
* and <tt>numberOfTics</tt>. If <tt>automaticTicCalculation == true</tt>
* appropriated values for these fields are calculated.
*/
double[] calculateTics() {
if (automaticTicCalculation) {
calculateTicsParameters();
}
double[] result = new double[numberOfTics];
if (numberOfTics > 0) {
double b = Util.log(minimumTic, logScale);
double a = Util.log(maximumTic, logScale);
a = numberOfTics > 1 ? (a - b) / (numberOfTics - 1) : 0;
for (int i = 0; i < result.length; i++) {
result[i] = Util.exp(a * i + b, logScale);
}
result[0] = adjust(minimum, result[0]);
result[numberOfTics - 1] = adjust(maximum, result[numberOfTics - 1]);
}
return result;
}
private void calculateTicsParameters() {
double min = Math.min(minimum, maximum);
double max = Math.max(minimum, maximum);
if (logScale) {
int minExponent = (int) (199.9999 + Math.log(min) / LN10) - 199;
int maxExponent = (int) (200.0001 + Math.log(max) / LN10) - 200;
minimumTic = Math.exp(LN10 * minExponent);
maximumTic = Math.exp(LN10 * maxExponent);
numberOfTics = maxExponent - minExponent + 1;
} else {
int baseExponent = (int) (199.69 + Math.log(max - min) / LN10) - 200;
double base = 0.2 * Math.exp(LN10 * baseExponent);
do
{
base *= 5;
int minInt = (int) (999999.999999 + min / base) - 999999;
int maxInt = (int) (1000000.000001 + max / base) - 1000000;
minimumTic = minInt * base;
maximumTic = maxInt * base;
numberOfTics = maxInt - minInt + 1;
} while (numberOfTics > 11);
}
}
/**
* Returns <tt>adjustingValue</tt> if <tt>value</tt> is very close
* to <tt>adjustingValue</tt>. Otherwise <tt>value</tt> is returned.
*/
private static double adjust(double adjustingValue, double value) {
return value != 0 && Math.abs(adjustingValue / value - 1) < 1e-11
? adjustingValue : value;
}
/**
* Returns a <tt>Properties</tt> object with those default parameters
* which are common for x- and y-axis.
*/
private static Properties createDefaultAxisProperties() {
Properties p = new Properties();
p.put(LOG_SCALE_KEY, "false");
p.put(MINIMUM_KEY, "0");
p.put(MAXIMUM_KEY, "1");
p.put(AXIS_LENGTH_KEY, "0.8");
p.put(AXIS_ATTRIBUTES_KEY + '/' + Factory.CLASS_NAME_KEY,
ShapeAttributes.class.getName());
p.put(AXIS_LABEL_KEY, "x");
p.put(AXIS_LABEL_POSITION_KEY, "0 -0.05");
p.put(AXIS_LABEL_ATTRIBUTES_KEY + '/' + Factory.CLASS_NAME_KEY,
BasicGraphicAttributes.class.getName());
p.put(AXIS_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.HORIZONTAL_ANCHOR_KEY, "center");
p.put(AUTOMATIC_TIC_CALCULATION_KEY, "true");
p.put(TIC_LENGTH_KEY, "0.01");
p.put(TIC_ATTRIBUTES_KEY + '/' + Factory.CLASS_NAME_KEY,
ShapeAttributes.class.getName());
p.put(TIC_LABEL_POSITION_KEY, "0 -0.01");
p.put(TIC_LABEL_FORMAT_KEY, "%1.1f");
p.put(TIC_LABEL_ATTRIBUTES_KEY + '/' + Factory.CLASS_NAME_KEY,
BasicGraphicAttributes.class.getName());
p.put(TIC_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.HORIZONTAL_ANCHOR_KEY, "center");
p.put(TIC_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.VERTICAL_ANCHOR_KEY, "top");
p.put(GRID_KEY, "false");
p.put(GRID_ATTRIBUTES_KEY + '/' + Factory.CLASS_NAME_KEY,
ShapeAttributes.class.getName());
return p;
}
/**
* Returns a <tt>Properties</tt> object of the default parameters for
* an x-axis.
*/
private static Properties createDefaultXAxisProperties() {
Properties p = createDefaultAxisProperties();
p.put(AXIS_LABEL_KEY, "x");
p.put(AXIS_LABEL_POSITION_KEY, "0 -0.05");
p.put(AXIS_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.VERTICAL_ANCHOR_KEY, "top");
p.put(TIC_LABEL_POSITION_KEY, "0 -0.01");
p.put(TIC_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.HORIZONTAL_ANCHOR_KEY, "center");
p.put(TIC_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.VERTICAL_ANCHOR_KEY, "top");
return p;
}
/**
* Creates an x axis based on the specified configuration parameters.
* All numbers (lengths, fontsizes, linethicknesses, etc.) are in
* device-independent units.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>automaticTicCalculation = true</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>Has to be <tt>true</tt> if the tics should be calculated
* automatically.</td></tr>
* <tr><td><tt>axisAttributes = </tt>default values of
* {@link ShapeAttributes}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Attributes of the axis box.</td></tr>
* <tr><td><tt>axisLabel = x</tt></td>
* <td><tt>String</tt></td><td>no</td>
* <td>Axis label.</td></tr>
* <tr><td><tt>axisLabelAttributes = </tt>default values of
* {@link BasicGraphicAttributes} with a text anchor CENTER
* TOP.</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Text attributes of axis label.</td></tr>
* <tr><td><tt>axisLabelPosition = 0 -0.05</tt></td>
* <td><tt>double[]</tt></td><td>no</td>
* <td>Position of the anchor of the axis
* label relative to the center of the x-axis line.</td></tr>
* <tr><td><tt>axisLength = 0.8</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Length of the x-axis.</td></tr>
* <tr><td><tt>grid = false</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>If <tt>true</tt> grid lines will be drawn through the axis
* tics.</td></tr>
* <tr><td><tt>gridAttributes = </tt>default values of
* {@link ShapeAttributes}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Attributes of the grid lines.</td></tr>
* <tr><td><tt>logScale = false</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>If <tt>true</tt> the axis will be logarithmic. Otherwise
* the axis is linear.</td></tr>
* <tr><td><tt>maximum = 1</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>The corresponding data value of one end of the axis.</td></tr>
* <tr><td><tt>maximumTic = </tt>result from automatic calculation</td>
* <td><tt>double</tt></td><td>no</td>
* <td>The corresponding data value of the tic nearest the maximum end
* of the axis.</td></tr>
* <tr><td><tt>minimum = 0</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>The corresponding data value of one end of the axis.</td></tr>
* <tr><td><tt>minimumTic = </tt>result from automatic calculation</td>
* <td><tt>double</tt></td><td>no</td>
* <td>The corresponding data value of the tic nearest the minimum end
* of the axis.</td></tr>
* <tr><td><tt>numberOfTics = </tt>result from automatic calculation</td>
* <td><tt>int</tt></td><td>no</td>
* <td>Number of tics. The tics between the minimum and maximum tic
* are spaced equidistantly.</td></tr>
* <tr><td><tt>ticAttributes = </tt>default values of
* {@link ShapeAttributes}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Attributes of the tics.</td></tr>
* <tr><td><tt>ticLabelAttributes = </tt>default values of
* {@link BasicGraphicAttributes} with a text anchor CENTER
* TOP.</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Text attributes of tic labels.</td></tr>
* <tr><td><tt>ticLabelFormat = %1.1f</tt></td>
* <td><tt>String</tt> or <tt>ConfigParameters</tt></td><td>no</td>
* <td>Defines rendering of the tic label. By default a
* <tt>printf</tt>-like format string is given (see {@link Format}).
* Note, that an empty string means that tic labels are dropped.
* <p>
* For non-numerical rendering an implementation of a
* {@link TicLabelFormat} can be specified (e.g.
* {@link TicLabelMap}). Note, that a configuration sub tree with
* a <tt>className</tt> key-value pair overwrites any string
* definition.</td></tr>
* <tr><td><tt>ticLabelPosition = 0 -0.01</tt></td>
* <td><tt>double[]</tt></td><td>no</td>
* <td>Position of the anchor of the tic label relative to the
* tic position on the axis.</td></tr>
* <tr><td><tt>ticLength = 0.01</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Length of the tics. Negative/positive values mean tics
* inside/outside the box.</td></tr>
* </table>
*/
public static AxisParameters createXAxis(ConfigParameters config) {
return createAxis(config, createDefaultXAxisProperties());
}
/**
* Returns a <tt>Properties</tt> object of the default parameters for
* an x-axis.
*/
private static Properties createDefaultYAxisProperties() {
Properties p = createDefaultAxisProperties();
p.put(AXIS_LENGTH_KEY, "0.45");
p.put(AXIS_LABEL_KEY, "y");
p.put(AXIS_LABEL_POSITION_KEY, "-0.1 0");
p.put(AXIS_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.VERTICAL_ANCHOR_KEY, "bottom");
p.put(AXIS_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.ORIENTATION_ANGLE_KEY, "90");
p.put(TIC_LABEL_POSITION_KEY, "-0.01 0");
p.put(TIC_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.HORIZONTAL_ANCHOR_KEY, "right");
p.put(TIC_LABEL_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.VERTICAL_ANCHOR_KEY, "center");
return p;
}
/**
* Creates an y axis based on the specified configuration parameters.
* All numbers (lengths, fontsizes, linethicknesses, etc.) are in
* device-independent units.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>automaticTicCalculation = true</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>Has to be <tt>true</tt> if the tics should be calculated
* automatically.</td></tr>
* <tr><td><tt>axisAttributes = </tt>default values of
* {@link ShapeAttributes}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Attributes of the axis box.</td></tr>
* <tr><td><tt>axisLabel = y</tt></td>
* <td><tt>String</tt></td><td>no</td>
* <td>Axis label.</td></tr>
* <tr><td><tt>axisLabelAttributes = </tt>default values of
* {@link BasicGraphicAttributes} with a text anchor CENTER
* BOTTOM and the text rotated by 90 degree.</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Text attributes of axis label.</td></tr>
* <tr><td><tt>axisLabelPosition = -0.1 0</tt></td>
* <td><tt>double[]</tt></td><td>no</td>
* <td>Position of the anchor of the axis
* label relative to the center of the y-axis line.</td></tr>
* <tr><td><tt>axisLength = 0.45</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Length of the y-axis.</td></tr>
* <tr><td><tt>grid = false</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>If <tt>true</tt> grid lines will be drawn through the axis
* tics.</td></tr>
* <tr><td><tt>gridAttributes = </tt>default values of
* {@link ShapeAttributes}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Attributes of the grid lines.</td></tr>
* <tr><td><tt>logScale = false</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>If <tt>true</tt> the axis will be logarithmic. Otherwise
* the axis is linear.</td></tr>
* <tr><td><tt>maximum = 1</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>The corresponding data value of one end of the axis.</td></tr>
* <tr><td><tt>maximumTic = </tt>result from automatic calculation</td>
* <td><tt>double</tt></td><td>no</td>
* <td>The corresponding data value of the tic nearest the maximum end
* of the axis.</td></tr>
* <tr><td><tt>minimum = 0</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>The corresponding data value of one end of the axis.</td></tr>
* <tr><td><tt>minimumTic = </tt>result from automatic calculation</td>
* <td><tt>double</tt></td><td>no</td>
* <td>The corresponding data value of the tic nearest the minimum end
* of the axis.</td></tr>
* <tr><td><tt>numberOfTics = </tt>result from automatic calculation</td>
* <td><tt>int</tt></td><td>no</td>
* <td>Number of tics. The tics between the minimum and maximum tic
* are spaced equidistantly.</td></tr>
* <tr><td><tt>ticAttributes = </tt>default values of
* {@link ShapeAttributes}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Attributes of the tics.</td></tr>
* <tr><td><tt>ticLabelAttributes = </tt>default values of
* {@link BasicGraphicAttributes} with a text anchor RIGHT CENTER.
* </td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Text attributes of tic labels.</td></tr>
* <tr><td><tt>ticLabelFormat = %1.1f</tt></td>
* <td><tt>String</tt></td><td>no</td>
* <td>Defines rendering of the tic label. By default a
* <tt>printf</tt>-like format string is given (see {@link Format}).
* Note, that an empty string means that tic labels are dropped.
* <p>
* For non-numerical rendering an implementation of a
* {@link TicLabelFormat} can be specified (e.g.
* {@link TicLabelMap}). Note, that a configuration sub tree with
* a <tt>className</tt> key-value pair overwrites any string
* definition.</td></tr>
* <tr><td><tt>ticLabelPosition = -0.01 0</tt></td>
* <td><tt>double[]</tt></td><td>no</td>
* <td>Position of the anchor of the tic label relative to the
* tic position on the axis.</td></tr>
* <tr><td><tt>ticLength = 0.01</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Length of the tics. Negative/positive values mean tics
* inside/outside the box.</td></tr>
* </table>
*/
public static AxisParameters createYAxis(ConfigParameters config) {
return createAxis(config, createDefaultYAxisProperties());
}
private static AxisParameters createAxis(ConfigParameters config,
Properties p) {
ConfigData cd = new PropertiesBasedConfigData(p);
ConfigParameters c = new ConfigParameters(cd);
cd = new ConfigParametersBasedConfigData(config, c);
c = new ConfigParameters(cd);
AxisParameters a = new AxisParameters();
a.logScale = c.getBoolean(LOG_SCALE_KEY);
a.minimum = c.getDouble(MINIMUM_KEY);
a.maximum = c.getDouble(MAXIMUM_KEY);
a.axisLength = c.getDouble(AXIS_LENGTH_KEY);
a.axisAttributes
= (LineAttributes) Factory.create(c.getNode(AXIS_ATTRIBUTES_KEY));
a.axisLabel = c.get(AXIS_LABEL_KEY);
a.axisLabelPosition
= new GraphPoint(c.getDoubleArray(AXIS_LABEL_POSITION_KEY));
a.axisLabelAttributes = (TextAttributes) Factory.create(
c.getNode(AXIS_LABEL_ATTRIBUTES_KEY));
a.ticLength = c.getDouble(TIC_LENGTH_KEY);
a.automaticTicCalculation
= c.getBoolean(AUTOMATIC_TIC_CALCULATION_KEY);
if (!a.automaticTicCalculation) {
a.calculateTicsParameters(); // calculate default parameters
a.minimumTic = c.getDouble(MINIMUM_TIC_KEY, a.minimumTic);
a.maximumTic = c.getDouble(MAXIMUM_TIC_KEY, a.maximumTic);
a.numberOfTics = c.getInt(NUMBER_OF_TICS_KEY, a.numberOfTics);
}
a.ticAttributes
= (LineAttributes) Factory.create(c.getNode(TIC_ATTRIBUTES_KEY));
a.ticLabelFormat = createTicLabelFormat(c);
a.ticLabelPosition
= new GraphPoint(c.getDoubleArray(TIC_LABEL_POSITION_KEY));
a.ticLabelAttributes = (TextAttributes) Factory.create(
c.getNode(TIC_LABEL_ATTRIBUTES_KEY));
a.grid = c.getBoolean(GRID_KEY);
a.gridAttributes
= (LineAttributes) Factory.create(c.getNode(GRID_ATTRIBUTES_KEY));
return a;
}
private static TicLabelFormat createTicLabelFormat(ConfigParameters c)
{
TicLabelFormat result = Format.create(c, TIC_LABEL_FORMAT_KEY);
ConfigParameters node = c.getNode(TIC_LABEL_FORMAT_KEY);
if (node.get(Factory.CLASS_NAME_KEY, null) != null) {
result = (TicLabelFormat) Factory.create(node);
}
return result;
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.GraphPoint;
import jcckit.graphic.GraphicAttributes;
import jcckit.graphic.GraphicalElement;
import jcckit.graphic.Rectangle;
import jcckit.util.ConfigParameters;
/**
* A factory of bars. The bars are {@link Rectangle Rectangles}.
* Depending on the configuration parameters the bars can be
* horizontal or vertical. Bars of several curves can be side by side or
* stacked. The bar length is determined by the x or y value of the
* curve point in device-independent coordinates. If the value is negative
* the bar goes into the negative direction. For stacked bars the values
* should always be positive.
* <p>
* When used inside a {@link SimpleCurve} soft clipping should always be
* switched off (see
* {@link SimpleCurve#SimpleCurve(ConfigParameters, int, int, ClippingShape, Legend)}).
*
* @author Franz-Josef Elmer
*/
public class BarFactory extends AbstractSymbolFactory {
/** Configuration parameter key. */
public static final String STACKED_KEY = "stacked",
HORIZONTAL_BARS_KEY = "horizontalBars";
private final boolean _stacked;
private final boolean _horizontalBars;
/**
* Creates an instance from the specified configuration parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>horizontalBars = false</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>If <tt>true</tt> horizontal bars will be drawn. Otherwise
* vertical bars are drawn.</td></tr>
* <tr><td><tt>stacked = false</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>If <tt>true</tt> the bars of several curves will be
* stacked.</td></tr>
* </table>
* In addition the configuration parameters of the
* <a href="AbstractSymbolFactory.html#AbstractSymbolFactory(jcckit.util.ConfigParameters)">
* constructor</a> of the superclass {@link AbstractSymbolFactory} apply.
*/
public BarFactory(ConfigParameters config) {
super(config);
_horizontalBars = config.getBoolean(HORIZONTAL_BARS_KEY, false);
_stacked = config.getBoolean(STACKED_KEY, false);
}
/**
* Creates a bar at the specified point.
* If <tt>hintFromPreviousCurve</tt>
* is not an instance of {@link PositionHint} the values of
* origin and position will be (0,0).
* @param hintFromPreviousCurve Hint from previous curve. Will be used
* to calculate symbol shape and hint for the next curve.
*/
protected Symbol createSymbol(GraphPoint point, GraphicAttributes attributes,
Hint hintForNextPoint,
Hint hintFromPreviousCurve) {
GraphPoint origin = new GraphPoint(null);
GraphPoint position = origin;
if (hintFromPreviousCurve instanceof PositionHint) {
origin = ((PositionHint) hintFromPreviousCurve).getOrigin();
position = ((PositionHint) hintFromPreviousCurve).getPosition();
}
double px = position.getX();
double py = position.getY();
double x = point.getX() - origin.getX();
double y = point.getY() - origin.getY();
if (_horizontalBars) {
y = _size;
position = new GraphPoint(px + 0.5 * x, point.getY() + py);
px += _stacked ? x : 0;
py += _stacked ? 0 : _size;
} else {
x = _size;
position = new GraphPoint(point.getX() + px, py + 0.5 * y);
px += _stacked ? 0 : _size;
py += _stacked ? y : 0;
}
Hint hintForNextCurve = new PositionHint(origin, new GraphPoint(px, py));
return new Symbol(new Rectangle(position, Math.abs(x), Math.abs(y),
attributes),
hintForNextPoint, hintForNextCurve);
}
/**
* Creates a symbol for the legend at the specified position.
* @param centerPosition Center position of the symbol.
* @param size The size of the symbol.
*/
public GraphicalElement createLegendSymbol(GraphPoint centerPosition,
double size) {
return new Rectangle(centerPosition, size, size, _attributes);
}
/**
* Returns <tt>null</tt> because this method isn't needed but has to be
* implemented.
*/
protected GraphicalElement createPlainSymbol(
GraphPoint centerPosition, double size, GraphicAttributes attributes) {
return null;
}
}

View File

@ -0,0 +1,206 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.data.DataPoint;
import jcckit.graphic.ClippingRectangle;
import jcckit.graphic.ClippingShape;
import jcckit.graphic.GraphicalComposite;
import jcckit.graphic.GraphicalElement;
import jcckit.graphic.GraphicAttributes;
import jcckit.graphic.GraphPoint;
import jcckit.graphic.LineAttributes;
import jcckit.graphic.Polygon;
import jcckit.graphic.Text;
import jcckit.transformation.CartesianTransformation;
import jcckit.transformation.Transformation;
import jcckit.util.ConfigParameters;
/**
* A Cartesian coordinate system. One or both axes can be logarithmic.
*
* @author Franz-Josef Elmer
*/
public class CartesianCoordinateSystem implements CoordinateSystem {
/** Configuration parameter key. */
public static final String ORIGIN_KEY = "origin",
X_AXIS_KEY = "xAxis",
Y_AXIS_KEY = "yAxis";
private final CartesianTransformation _transformation;
private final GraphicalComposite _view;
private final ClippingRectangle _clippingRectangle;
/**
* Creates an instance from the specified configuration parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>origin = 0.15,&nbsp;0.1</tt></td>
* <td><tt>double[]</tt></td><td>no</td>
* <td>Position (in device-independent coordinates) of the lower-left
* corner of the axis box.</td></tr>
* <tr><td><tt>xAxis</tt></td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Parameters defining the x-axis. For definitions and default
* values see {@link AxisParameters#createXAxis
* AxisParameters.createXAxis()}.</td></tr>
* <tr><td><tt>yAxis</tt></td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Parameters defining the y-axis. For definitions and default
* values see {@link AxisParameters#createYAxis
* AxisParameters.createYAxis()}.</td></tr>
* </table>
*/
public CartesianCoordinateSystem(ConfigParameters config) {
this(new GraphPoint(config.getDoubleArray(ORIGIN_KEY,
new double[] {0.15, 0.1})),
AxisParameters.createXAxis(config.getNode(X_AXIS_KEY)),
AxisParameters.createYAxis(config.getNode(Y_AXIS_KEY)));
}
/**
* Creates an instance for the specified origin and parameters
* of both axes.
* @param origin Position (in device-independent coordinates) of the
* lower-left corner of the axis box.
* @param xAxisParameters Parameters of the x-axis.
* @param yAxisParameters Parameters of the y-axis.
*/
public CartesianCoordinateSystem(GraphPoint origin,
AxisParameters xAxisParameters,
AxisParameters yAxisParameters) {
double x = origin.getX();
double y = origin.getY();
_transformation = new CartesianTransformation(xAxisParameters.logScale,
yAxisParameters.logScale,
new DataPoint(xAxisParameters.minimum, yAxisParameters.minimum),
new GraphPoint(x, y),
new DataPoint(xAxisParameters.maximum, yAxisParameters.maximum),
new GraphPoint(x + xAxisParameters.axisLength,
y + yAxisParameters.axisLength));
_clippingRectangle = new ClippingRectangle(x, y,
x + xAxisParameters.axisLength,
y + yAxisParameters.axisLength);
_view = new GraphicalComposite(null);
createView(origin, xAxisParameters, yAxisParameters);
}
/** Creates the graphical representation of this coordinate system. */
private void createView(GraphPoint origin,
AxisParameters xAxisParameters,
AxisParameters yAxisParameters) {
double x0 = origin.getX();
double x1 = x0 + xAxisParameters.axisLength;
double y0 = origin.getY();
double y1 = y0 + yAxisParameters.axisLength;
GraphPoint lowerLeftCorner = new GraphPoint(x0, y0);
GraphPoint upperLeftCorner = new GraphPoint(x0, y1);
GraphPoint lowerRightCorner = new GraphPoint(x1, y0);
GraphPoint upperRightCorner = new GraphPoint(x1, y1);
LineAttributes xLineAttributes = xAxisParameters.axisAttributes;
LineAttributes yLineAttributes = yAxisParameters.axisAttributes;
createTicsAndGrid(true, y0, y1, xAxisParameters);
createTicsAndGrid(false, x0, x1, yAxisParameters);
addLine(lowerLeftCorner, lowerRightCorner, xLineAttributes);
addLine(lowerLeftCorner, upperLeftCorner, yLineAttributes);
addLine(upperLeftCorner, upperRightCorner, xLineAttributes);
addLine(lowerRightCorner, upperRightCorner, yLineAttributes);
createLabel(0.5 * (x0 + x1), y0, xAxisParameters);
createLabel(x0, 0.5 * (y0 + y1), yAxisParameters);
}
private void createLabel(double x, double y, AxisParameters parameters) {
if (parameters.axisLabel.length() > 0) {
_view.addElement(new Text(
new GraphPoint(x + parameters.axisLabelPosition.getX(),
y + parameters.axisLabelPosition.getY()),
parameters.axisLabel, parameters.axisLabelAttributes));
}
}
private void createTicsAndGrid(boolean isXAxis, double low, double high,
AxisParameters parameters) {
double[] tics = parameters.calculateTics();
int offIndex = isXAxis ? 1 : 0;
double[] point = new double[2]; // helper array
for (int i = 0; i < tics.length; i++) {
point[1 - offIndex] = tics[i];
point[offIndex] = 1;
GraphPoint gPoint1 =
_transformation.transformToGraph(new DataPoint(point[0], point[1]));
point[0] = gPoint1.getX();
point[1] = gPoint1.getY();
point[offIndex] = high;
gPoint1 = new GraphPoint(point[0], point[1]);
point[offIndex] += parameters.ticLength;
addLine(gPoint1, new GraphPoint(point[0], point[1]),
parameters.ticAttributes);
point[offIndex] = low;
GraphPoint gPoint2 = new GraphPoint(point[0], point[1]);
if (parameters.grid) {
addLine(gPoint1, gPoint2, parameters.gridAttributes);
}
point[offIndex] -= parameters.ticLength;
addLine(gPoint2, new GraphPoint(point[0], point[1]),
parameters.ticAttributes);
if (parameters.ticLabelFormat != null) {
point[offIndex] += parameters.ticLength;
point[0] += parameters.ticLabelPosition.getX();
point[1] += parameters.ticLabelPosition.getY();
_view.addElement(new Text(new GraphPoint(point[0], point[1]),
parameters.ticLabelFormat.form(tics[i]),
parameters.ticLabelAttributes));
}
}
}
private void addLine(GraphPoint point1, GraphPoint point2,
GraphicAttributes attributes) {
Polygon line = new Polygon(attributes, false);
line.addPoint(point1);
line.addPoint(point2);
_view.addElement(line);
}
/**
* Returns the graphical representation of the coordinate system.
* In each call the same instance is returned.
*/
public GraphicalElement getView() {
return _view;
}
/**
* Returns the clipping rectangle of specified by the axis.
* In each call the same instance is returned.
*/
public ClippingShape getClippingShape() {
return _clippingRectangle;
}
/**
* Returns the transformation of data coordinates into the device-independent
* coordinates of the axis box.
* In each call the same instance is returned.
*/
public Transformation getTransformation() {
return _transformation;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.GraphicAttributes;
import jcckit.graphic.GraphicalElement;
import jcckit.graphic.GraphPoint;
import jcckit.graphic.Oval;
import jcckit.util.ConfigParameters;
/**
* A factory of circle symbols.
*
* @author Franz-Josef Elmer
*/
public class CircleSymbolFactory extends AbstractSymbolFactory {
/**
* Creates an instance from the specified configuration parameters.
* For the configuration parameters see the
* <a href="AbstractSymbolFactory.html#AbstractSymbolFactory(jcckit.util.ConfigParameters)">
* constructor</a> of the superclass {@link AbstractSymbolFactory}.
*/
public CircleSymbolFactory(ConfigParameters config) {
super(config);
}
/**
* Creates a circle.
* @param centerPosition Position of the center of the circle.
* @param size Diameter of the circle.
* @param attributes Circle attributes.
*/
protected GraphicalElement createPlainSymbol(GraphPoint centerPosition,
double size,
GraphicAttributes attributes) {
return new Oval(centerPosition, size, size, attributes);
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.GraphicalElement;
import jcckit.graphic.ClippingShape;
import jcckit.transformation.Transformation;
/**
* Interface for all generators of coordinate systems. A
* <tt>CoordinateSystem</tt> creates a
* {@link jcckit.graphic.GraphicalComposite} which contains all the
* {@link GraphicalElement GraphicalElements} defining axes, labels, grid, etc.
*
* @author Franz-Josef Elmer
*/
public interface CoordinateSystem {
/**
* Returns the graphical representation of a coordinate
* system. Different invocations of this method may return
* different coordinate systems, e.g., due to changes in the
* transformation or clipping shapes.
*/
public GraphicalElement getView();
/**
* Returns the clipping chape of {@link Curve Curves} drawn on top
* of the coordinates system. Different invocations of
* this method may return different clipping shapes.
*/
public ClippingShape getClippingShape();
/**
* Returns the transformation between data coordinates and
* device-independent graphcial coordinates. Different invocations
* of this method may return different transformations.
*/
public Transformation getTransformation();
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.GraphicalElement;
import jcckit.graphic.GraphPoint;
/**
* A curve is defined by a sequence of points in device-independent
* coordinates. The points can be decorated by symbols and/or
* connected by lines.{@link Hint Hints} are used to determine additional
* properties of the symbol. This is especially important for
* charts with bars.
* <p>
* In accordance with the Factory Method Pattern
* the symbols are created by a {@link SymbolFactory}.
*
* @author Franz-Josef Elmer
*/
public interface Curve {
/**
* Returns the graphical representation of a curve.
* Different invocations of this method might return
* different instances.
* This is especially true after adding, inserting, removing, or
* repplacing a point of the curve.
*/
public GraphicalElement getView();
/**
* Returns a symbol which can be used to create the legend for the curve.
* For example, it should return a horizontal line with the symbol
* in the middle if the curve is a line with points decorated by symbols.
*/
public GraphicalElement getLegendSymbol();
/**
* Appends a new point to the curve.
* @param point Position in device-independent coordinates.
* @param hintFromPreviousCurve Hint which may be used to calculate
* the corresponding {@link GraphicalElement}.
* @return hint for next curve.
*/
public Hint addPoint(GraphPoint point, Hint hintFromPreviousCurve);
/** Removes all points from the curve. */
public void removeAllPoints();
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.ClippingShape;
/**
* Interface of a curve factory. A curve factory creates a new instance
* of a {@link Curve}.
*
* @author Franz-Josef Elmer
*/
public interface CurveFactory {
/**
* Creates a new curve instance.
* @param curveIndex The index of the curve in the {@link Plot} to which
* it should belong.
* @param numberOfCurves Number of curves. Will be needed to calculate the
* y-coordinate of the legend symbol.
* @param clippingShape Clipping shape applied to the curve.
* @param legend The legend which will show the curve symbol.
* @return an empty instance.
*/
public Curve create(int curveIndex, int numberOfCurves,
ClippingShape clippingShape, Legend legend);
}

View File

@ -0,0 +1,136 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.GraphPoint;
import jcckit.graphic.GraphicAttributes;
import jcckit.graphic.GraphicalComposite;
import jcckit.graphic.GraphicalElement;
import jcckit.graphic.Rectangle;
import jcckit.util.ConfigParameters;
import jcckit.util.Factory;
/**
* Symbol factory for creating symbols with error bars. It wraps
* a {@link SymbolFactory} for creating the symbol. The error bars
* are {@link Rectangle Rectangles}.
* <p>
* Curves with error bars are based on <em>two</em>
* {@link jcckit.data.DataCurve DataCurves}:
* <ol><li>The plain curve.
* <li>An instance which stores the errors in <i>x</i> and <i>y</i>.
* It is assumed that the errors are positive values defining
* the error symmetrically around the curve points.
* </ol>
* <p>
* The <tt>ErrorBarFactory</tt> needs an instance of {@link PositionHint}
* as initial {@link Hint} for the next curve. Its origin must be
* the origin of the data coordinate system in device-independent coordinates.
* The position of <tt>PositionHint</tt> must be undefined.
*
* @author Franz-Josef Elmer
*/
public class ErrorBarFactory implements SymbolFactory {
/** Configuration parameter key. */
public static final String SYMBOL_FACTORY_KEY = "symbolFactory";
private final SymbolFactory _symbolFactory;
private final GraphicAttributes _attributes;
private final double _size;
/**
* Creates an instance from the specified configuration parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>symbolFactory = null</tt></td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Definition of the wrapped {@link SymbolFactory} which generates
* the curve symbol without bars. By default an empty
* {@link GraphicalComposite} will be created.</td></tr>
* <tr><td><tt>size = 0</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Width of the error bars.</td></tr>
* <tr><td><tt>attributes = null</tt></td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Definition of the {@link GraphicAttributes} of the error
* bars.</td></tr>
* </table>
*/
public ErrorBarFactory(ConfigParameters config) {
_symbolFactory = (SymbolFactory) Factory.createOrGet(
config.getNode(SYMBOL_FACTORY_KEY), null);
_size = config.getDouble(SIZE_KEY, 0);
_attributes = (GraphicAttributes) Factory.createOrGet(
config.getNode(ATTRIBUTES_KEY), null);
}
/**
* Creates the legend symbol. Calls the wrapped {@link SymbolFactory}
* or returns an empty instance of {@link GraphicalComposite} if undefined.
*/
public GraphicalElement createLegendSymbol(GraphPoint centerPosition,
double size) {
return _symbolFactory == null ? new GraphicalComposite(null)
: _symbolFactory.createLegendSymbol(centerPosition, size);
}
/**
* Creates either the curve symbol or the error bars. Error bars are
* created when <tt>hintFromPreviousCurve</tt> is an instance of
* {@link PositionHint} and its position attribute is not <tt>null</tt>.
* Otherwise the curve symbol is created. The position attributes stores
* the curve point (in device-independent coordinates). The origin is
* always as set in the initial <tt>PositionHint</tt>. The hint for
* the next curve wrapped by the returned <tt>Symbol</tt> is always
* a <tt>PositionHint</tt>.
*/
public Symbol createSymbol(GraphPoint point, Hint hintFromPreviousPoint,
Hint hintFromPreviousCurve) {
GraphPoint origin = new GraphPoint(null);
GraphPoint position = null;
if (hintFromPreviousCurve instanceof PositionHint) {
origin = ((PositionHint) hintFromPreviousCurve).getOrigin();
position = ((PositionHint) hintFromPreviousCurve).getPosition();
}
if (position == null) {
if (_symbolFactory == null) {
return new Symbol(new GraphicalComposite(null), hintFromPreviousPoint,
new PositionHint(origin, point));
} else {
return _symbolFactory.createSymbol(point, hintFromPreviousPoint,
new PositionHint(origin, point));
}
} else {
double xError = point.getX() - origin.getX();
double yError = point.getY() - origin.getY();
GraphicalComposite errorBars = new GraphicalComposite(null);
if (xError > 0) {
errorBars.addElement(new Rectangle(position, 2 * xError, _size,
_attributes));
}
if (yError > 0) {
errorBars.addElement(new Rectangle(position, _size, 2 * yError,
_attributes));
}
return new Symbol(errorBars, hintFromPreviousPoint,
new PositionHint(origin, null));
}
}
}

32
src/jcckit/plot/Hint.java Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
/**
* Marker interface of all types of hints. Hints are used to calculate
* {@link jcckit.graphic.GraphicalElement} representing a point in a {@link
* Curve}. For example, in a chart with stacked
* bars the data determines the height of a bar but the foot of
* a bar is determined by the height of the bar below. Its value will be
* stored in a {@link PositionHint}.
*
* @author Franz-Josef Elmer
*/
public interface Hint {}

250
src/jcckit/plot/Legend.java Normal file
View File

@ -0,0 +1,250 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.graphic.BasicGraphicAttributes;
import jcckit.graphic.GraphicalElement;
import jcckit.graphic.GraphicalComposite;
import jcckit.graphic.GraphicAttributes;
import jcckit.graphic.GraphPoint;
import jcckit.graphic.Polygon;
import jcckit.graphic.Rectangle;
import jcckit.graphic.ShapeAttributes;
import jcckit.graphic.Text;
import jcckit.graphic.TextAttributes;
import jcckit.util.ConfigData;
import jcckit.util.ConfigParameters;
import jcckit.util.ConfigParametersBasedConfigData;
import jcckit.util.Factory;
import jcckit.util.PropertiesBasedConfigData;
import java.util.Properties;
/**
* Helper class for creating the legend of a {@link Plot}.
*
* @author Franz-Josef Elmer
*/
public class Legend {
/** Configuration parameter key. */
public static final String UPPER_RIGHT_CORNER_KEY = "upperRightCorner",
BOX_WIDTH_KEY = "boxWidth",
BOX_HEIGHT_KEY = "boxHeight",
BOX_ATTRIBUTES_KEY = "boxAttributes",
TITLE_KEY = "title",
TITLE_DISTANCE_KEY = "titleDistance",
TITLE_ATTRIBUTES_KEY = "titleAttributes",
LEFT_DISTANCE_KEY = "leftDistance",
BOTTOM_DISTANCE_KEY = "bottomDistance",
TOP_DISTANCE_KEY = "topDistance",
LINE_LENGTH_KEY = "lineLength",
SYMBOL_SIZE_KEY = "symbolSize",
CURVE_TITLE_DISTANCE_KEY = "curveTitleDistance",
CURVE_TITLE_ATTRIBUTES_KEY
= "curveTitleAttributes";
private final GraphicalComposite _box;
private final TextAttributes _curveTitleAttributes;
private final double _xSymbol;
private final double _xText;
private final double _yBase;
private final double _yLastRow;
private final double _length;
private final double _size;
/**
* Creates an instance from the specified configuration parameters.
* All numbers (lengths, fontsizes, linethicknesses, etc.) are in
* device-independent units.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>bottomDistance = 0.02</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Distance between the last row and the bottom of the legend box.
* </td></tr>
* <tr><td><tt>boxAttributes = </tt>default values of
* {@link ShapeAttributes} with a white fill color.</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Attributes of the legend box.</td></tr>
* <tr><td><tt>boxHeight = 0.1</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Height of the legend box.</td></tr>
* <tr><td><tt>boxWidth = 0.2</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Width of the legend box.</td></tr>
* <tr><td><tt>curveTitleAttributes = </tt>default values of
* {@link BasicGraphicAttributes}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Text attributes of curve titles printed in the legend.</td></tr>
* <tr><td><tt>curveTitleDistance = 0.005</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Horizontal distance between the line part of the legend symbol
* and the curve title.</td></tr>
* <tr><td><tt>leftDistance = 0.01</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Horizontal distance between the line part of the legend symbol
* and the left border of the legend box.</td></tr>
* <tr><td><tt>lineLength = 0.035</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Length of the line part of the legend symbol.</td></tr>
* <tr><td><tt>symbolSize = 0.01</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Size of the symbol part of the legend symbol. Will be the
* <tt>size</tt> argument of {@link SymbolFactory#createLegendSymbol
* createLegendSymbol} in a {@link SymbolFactory}.</td></tr>
* <tr><td><tt>titleAttributes = </tt>default values of
* {@link BasicGraphicAttributes} with a text anchor CENTER
* TOP.</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Text attributes of the title of the legend box.</td></tr>
* <tr><td><tt>title = Legend</tt></td>
* <td><tt>String</tt></td><td>no</td>
* <td>Title of the legend box.</td></tr>
* <tr><td><tt>titleDistance = 0.005</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Distance between the center of the upper line of the legend box
* and the anchor of the legend title.</td></tr>
* <tr><td><tt>topDistance = 0.04</tt></td>
* <td><tt>double</tt></td><td>no</td>
* <td>Distance between the first row and the top of the legend box.
* </td></tr>
* <tr><td><tt>upperRightCorner = 0.94,&nbsp;0.54</tt></td>
* <td><tt>double[]</tt></td><td>no</td>
* <td>Position of the upper-right corner of the legend box.</td></tr>
* </table>
*/
public Legend(ConfigParameters config) {
config = mergeWithDefaultConfig(config);
GraphPoint corner
= new GraphPoint(config.getDoubleArray(UPPER_RIGHT_CORNER_KEY,
new double[] {0.94, 0.54}));
double width = config.getDouble(BOX_WIDTH_KEY, 0.2);
double height = config.getDouble(BOX_HEIGHT_KEY, 0.1);
_curveTitleAttributes = (TextAttributes) Factory.create(
config.getNode(CURVE_TITLE_ATTRIBUTES_KEY));
_xSymbol = corner.getX() - width
+ config.getDouble(LEFT_DISTANCE_KEY, 0.01);
_yBase = corner.getY() - config.getDouble(TOP_DISTANCE_KEY, 0.04);
_yLastRow = corner.getY() - height
+ config.getDouble(BOTTOM_DISTANCE_KEY, 0.02);
_length = config.getDouble(LINE_LENGTH_KEY, 0.035);
_size = config.getDouble(SYMBOL_SIZE_KEY, 0.01);
_xText = _xSymbol + _length
+ config.getDouble(CURVE_TITLE_DISTANCE_KEY, 0.005);
_box = new GraphicalComposite(null);
double xCenter = corner.getX() - width / 2;
_box.addElement(new Rectangle(
new GraphPoint(xCenter, corner.getY() - height / 2), width, height,
(GraphicAttributes) Factory.create(
config.getNode(BOX_ATTRIBUTES_KEY))));
_box.addElement(new Text(
new GraphPoint(xCenter, corner.getY()
- config.getDouble(TITLE_DISTANCE_KEY, 0.005)),
config.get(TITLE_KEY, "Legend"),
(TextAttributes) Factory.create(
config.getNode(TITLE_ATTRIBUTES_KEY))));
}
private ConfigParameters mergeWithDefaultConfig(ConfigParameters config) {
Properties p = new Properties();
p.put(BOX_ATTRIBUTES_KEY + '/' + Factory.CLASS_NAME_KEY,
ShapeAttributes.class.getName());
p.put(BOX_ATTRIBUTES_KEY + '/'
+ ShapeAttributes.FILL_COLOR_KEY, "0xffffff");
p.put(BOX_ATTRIBUTES_KEY + '/'
+ ShapeAttributes.LINE_COLOR_KEY, "0");
p.put(TITLE_ATTRIBUTES_KEY + '/' + Factory.CLASS_NAME_KEY,
BasicGraphicAttributes.class.getName());
p.put(TITLE_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.HORIZONTAL_ANCHOR_KEY, "center");
p.put(TITLE_ATTRIBUTES_KEY + '/'
+ BasicGraphicAttributes.VERTICAL_ANCHOR_KEY, "top");
p.put(CURVE_TITLE_ATTRIBUTES_KEY + '/' + Factory.CLASS_NAME_KEY,
BasicGraphicAttributes.class.getName());
ConfigData cd = new PropertiesBasedConfigData(p);
cd = new ConfigParametersBasedConfigData(config, new ConfigParameters(cd));
return new ConfigParameters(cd);
}
/**
* Returns the legend box with title but without legend symbols and curve
* titles.
*/
public GraphicalElement getBox() {
return _box;
}
/**
* Creates the symbol part of a legend symbol.
* @param curveIndex Index of the curve. Will be needed to calculate the
* y-coordinate of the symbol.
* @param numberOfCurves Number of curves. Will be needed to calculate the
* y-coordinate of the symbol.
* @param factory Factory for the symbol part of the legend symbol.
* Can be <tt>null</tt>.
* @param withLine <tt>true</tt> if the line part of the legend symbol
* should be created.
* @param lineAttributes Attributes of the line part.
*/
public GraphicalElement createSymbol(int curveIndex, int numberOfCurves,
SymbolFactory factory,
boolean withLine,
GraphicAttributes lineAttributes) {
GraphicalComposite result = new GraphicalComposite(null);
double y = calculateBaseLine(curveIndex, numberOfCurves);
if (withLine) {
Polygon line = new Polygon(lineAttributes, false);
line.addPoint(new GraphPoint(_xSymbol, y));
line.addPoint(new GraphPoint(_xSymbol + _length, y));
result.addElement(line);
}
if (factory != null) {
result.addElement(factory.createLegendSymbol(
new GraphPoint(_xSymbol + _length / 2, y), _size));
}
return result;
}
private double calculateBaseLine(int curveIndex, int numberOfCurves) {
if (numberOfCurves > 1) {
return _yBase + ((_yLastRow - _yBase) / (numberOfCurves - 1))
* curveIndex;
} else {
return 0.5 * (_yBase + _yLastRow);
}
}
/**
* Creates the title part of a legend symbol.
* @param curveIndex Index of the curve. Will be needed to calculate the
* y-coordinate of the title.
* @param numberOfCurves Number of curves. Will be needed to calculate the
* y-coordinate of the symbol.
* @param title Title text.
*/
public GraphicalElement createCurveTitle(int curveIndex, int numberOfCurves,
String title) {
return new Text(new GraphPoint(_xText, calculateBaseLine(curveIndex,
numberOfCurves)),
title, _curveTitleAttributes);
}
}

377
src/jcckit/plot/Plot.java Normal file
View File

@ -0,0 +1,377 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import java.util.Vector;
import jcckit.data.DataCurve;
import jcckit.data.DataEvent;
import jcckit.data.DataListener;
import jcckit.data.DataPlot;
import jcckit.data.DataPoint;
import jcckit.graphic.ClippingShape;
import jcckit.graphic.GraphPoint;
import jcckit.graphic.GraphicalComposite;
import jcckit.graphic.GraphicalElement;
import jcckit.transformation.Transformation;
import jcckit.util.ConfigParameters;
import jcckit.util.Factory;
/**
* A plot is determined by a {@link CoordinateSystem}, {@link Curve Curves},
* an optional annotation layer and an optional {@link Legend}. When rendered
* these components are draw in this order.
* <p>
* Registrated {@link PlotListener PlotListeners} will be informed
* when the plot changes.
* <p>
* A {@link DataPlot} can be connected with a <tt>Plot</tt> instance.
* This is done with the method {@link #connect connect()} which registrates
* this <tt>Plot</tt> instance as
* a {@link DataListener} at the connected <tt>DataPlot</tt>.
* After an received {@link DataEvent DataEvents} has been handled
* the registrated <tt>PlotListeners</tt> will receive a
* {@link PlotEvent} of the type {@link PlotEventType#DATA_PLOT_CHANGED}.
*
* @author Franz-Josef Elmer
*/
public class Plot implements DataListener {
/** Configuration parameter key. */
public static final String COORDINATE_SYSTEM_KEY = "coordinateSystem",
CURVE_FACTORY_KEY = "curveFactory",
LEGEND_VISIBLE_KEY = "legendVisible",
LEGEND_KEY = "legend",
INITIAL_HINT_FOR_NEXT_CURVE_KEY
= "initialHintForNextCurve";
private final Vector _plotListeners = new Vector();
private DataPlot _dataPlot;
private final CurveFactory _curveFactory;
private final Vector _curves = new Vector();
private final Vector _nextCurveHints = new Vector();
private final Hint _initialHintForNextCurve;
private final Legend _legend;
private final boolean _legendVisibility;
private GraphicalElement _coordinateSystemView;
private ClippingShape _clippingShape;
private Transformation _transformation;
private GraphicalElement _annotation;
private GraphicalComposite _legendView = new GraphicalComposite(null);
/**
* Creates an instance from the specified configuration parameters.
* <table border=1 cellpadding=5>
* <tr><th>Key &amp; Default Value</th><th>Type</th><th>Mandatory</th>
* <th>Description</th></tr>
* <tr><td><tt>coordinateSystem = </tt>{@link CartesianCoordinateSystem}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Definition of the {@link CoordinateSystem}.</td></tr>
* <tr><td><tt>curveFactory = </tt>{@link SimpleCurveFactory}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Definition of the {@link CurveFactory}.</td></tr>
* <tr><td><tt>initialHintForNextCurve = null</tt></td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Definition of the initial {@link Hint} which is needed by some
* {@link SymbolFactory SymbolFactories} like {@link BarFactory}.
* </td></tr>
* <tr><td><tt>legend = </tt>default values of {@link Legend}</td>
* <td><tt>ConfigParameters</tt></td><td>no</td>
* <td>Configuration parameters of a {@link Legend}.</td></tr>
* <tr><td><tt>legendVisible = true</tt></td>
* <td><tt>boolean</tt></td><td>no</td>
* <td>If <tt>true</tt> the {@link Legend} will be created.</td></tr>
* </table>
*/
public Plot(ConfigParameters config) {
CoordinateSystem coordinateSystem = (CoordinateSystem) Factory.create(
config.getNode(COORDINATE_SYSTEM_KEY),
CartesianCoordinateSystem.class.getName());
setCoordinateSystem(coordinateSystem);
_curveFactory = (CurveFactory) Factory.create(
config.getNode(CURVE_FACTORY_KEY),
SimpleCurveFactory.class.getName());
_initialHintForNextCurve = (Hint) Factory.createOrGet(
config.getNode(INITIAL_HINT_FOR_NEXT_CURVE_KEY), null);
_legend = new Legend(config.getNode(LEGEND_KEY));
_legendVisibility = config.getBoolean(LEGEND_VISIBLE_KEY, true);
}
/**
* Sets the coordinate system. All curves will be regenerated and a
* {@link PlotEvent} of type {@link PlotEventType#COODINATE_SYSTEM_CHANGED}
* will be fired.
*
* @param coordinateSystem New coordinate system.
*/
public void setCoordinateSystem(CoordinateSystem coordinateSystem)
{
_coordinateSystemView = coordinateSystem.getView();
_clippingShape = coordinateSystem.getClippingShape();
_transformation = coordinateSystem.getTransformation();
if (_dataPlot != null)
{
generateCurves(_dataPlot);
}
notifyListeners(
new PlotEvent(this, PlotEventType.COODINATE_SYSTEM_CHANGED, null));
}
/**
* Adds the specified {@link PlotListener}. Does nothing if
* already added.
*/
public void addPlotListener(PlotListener listener) {
if (!_plotListeners.contains(listener)) {
_plotListeners.addElement(listener);
}
}
/**
* Removes the specfied {@link PlotListener}. Does nothing if
* already removed.
*/
public void removePlotListener(PlotListener listener) {
_plotListeners.removeElement(listener);
}
/**
* Sends all registrated {@link PlotListener PlotListeners}
* the specified event.
*/
protected void notifyListeners(PlotEvent event) {
for (int i = 0, n = _plotListeners.size(); i < n; i++) {
((PlotListener) _plotListeners.elementAt(i)).plotChanged(event);
}
}
/**
* Connect the specified {@link DataPlot} with this instance.
* <p>
* If this <tt>Plot</tt> instance is already connected with a
* <tt>DataPlot</tt> the connection will be released and a
* {@link PlotEvent} of the type {@link PlotEventType#DATA_PLOT_DISCONNECTED}
* will be sent to all registrated {@link PlotListener PlotListeners}.
* <p>
* It registers itself at <tt>dataPlot</tt> and
* all its {@link DataCurve DataCurves}.
* <p>
* Finally all curves will be generated and a <tt>PlotEvent</tt>
* of the type {@link PlotEventType#DATA_PLOT_CONNECTED} will be transmitted.
* @param dataPlot Data to be connected with this plot instance.
* Can be <tt>null</tt> in order to disconnect this instance from
* any <tt>DataPlot</tt>.
*/
public void connect(DataPlot dataPlot) {
if (_dataPlot != null) {
_dataPlot.removeDataListener(this);
notifyListeners(new PlotEvent(this, PlotEventType.DATA_PLOT_DISCONNECTED,
_dataPlot));
}
_dataPlot = dataPlot;
if (_dataPlot != null)
{
_dataPlot.addDataListener(this);
generateCurves(_dataPlot);
notifyListeners(new PlotEvent(this, PlotEventType.DATA_PLOT_CONNECTED,
_dataPlot));
}
}
/**
* Transforms a point from device-independent coordinates into
* data coordinates.
* @param point Point in device-independent coordinates.
* @return transform <tt>point</tt>.
*/
public DataPoint transform(GraphPoint point) {
return _transformation.transformToData(point);
}
/**
* Creates a graphical representation of the complete plot.
* @return <tt>GraphicalComposite</tt> containing the views of the
* coordinate system, the curves, and optionally the legend (in this order).
*/
public GraphicalComposite getCompletePlot() {
GraphicalComposite result = new GraphicalComposite(null);
result.addElement(_coordinateSystemView);
GraphicalElement[] curves = getCurves();
for (int i = 0; i < curves.length; i++) {
result.addElement(curves[i]);
}
if (_annotation != null) {
result.addElement(_annotation);
}
if (_legendVisibility) {
result.addElement(getLegend());
}
return result;
}
/** Returns the view of the coordinate system. */
public GraphicalElement getCoordinateSystem() {
return _coordinateSystemView;
}
/** Returns the graphical representations of all curves. */
public GraphicalElement[] getCurves() {
synchronized (_curves) {
GraphicalElement[] curves = new GraphicalElement[_curves.size()];
for (int i = 0; i < curves.length; i++) {
curves[i] = ((Curve) _curves.elementAt(i)).getView();
}
return curves;
}
}
/**
* Returns the annotation layer.
* @return <tt>null</tt> if no annotation layer.
*/
public GraphicalElement getAnnotation()
{
return _annotation;
}
/**
* Sets the annotation layer.
* @param annotation Any kind of graphics which will be drawn on the
* top of the curves but may be covered by the legend.
* Can be <tt>null</tt>.
*/
public void setAnnotation(GraphicalElement annotation)
{
_annotation = annotation;
}
/** Returns <tt>true</tt> if the legend is visible. */
public boolean isLegendVisible() {
return _legendVisibility;
}
/** Returns the graphical representations of the legend. */
public GraphicalElement getLegend() {
return _legendView;
}
/**
* Handles the received {@link DataEvent} and notifies
* {@link PlotListener PlotListeners} by an event of the type
* {@link PlotEventType#DATA_CURVE_CHANGED} or
* {@link PlotEventType#DATA_PLOT_CHANGED}. The following table shows what
* this method does:
* <table border=1 cellpadding=5>
* <tr><th>Source of <tt>event</tt></th>
* <th>All hints for the next curve are <tt>null</tt>?</th>
* <th>Action</th><th>Type of sent {@link PlotEvent}</th></tr>
* <tr><td>{@link DataCurve}</td><td>Yes</td><td>Recreate changed curve.<td>
* <td><tt>DATA_CURVE_CHANGED</tt></td></tr>
* <tr><td>{@link DataCurve}</td><td>No</td><td>Recreate changed curve
* and all curves with large curve index.<td>
* <td><tt>DATA_PLOT_CHANGED</tt></td></tr>
* <tr><td>{@link DataPlot}</td><td>-</td><td>Recreate all curves
* and {@link Legend} view.<td>
* <td><tt>DATA_PLOT_CHANGED</tt></td></tr>
* </table>
*/
public void dataChanged(DataEvent event) {
Integer index = new Integer(0);
PlotEventType type = PlotEventType.DATA_PLOT_CHANGED;
synchronized (_curves) {
int numberOfCurves = _curves.size();
if (event.getContainer() instanceof DataCurve
&& numberOfCurves == _dataPlot.getNumberOfElements()) {
DataCurve curve = (DataCurve) event.getContainer();
index = new Integer(curve.getContainer().getIndexOf(curve));
type = PlotEventType.DATA_CURVE_CHANGED;
fillCurve(index.intValue(), curve);
if (index.intValue() < numberOfCurves - 1) {
Vector curveHints
= (Vector) _nextCurveHints.elementAt(index.intValue());
for (int i = 0, n = curveHints.size(); i < n; i++) {
if (curveHints.elementAt(i) != null) {
type = PlotEventType.DATA_PLOT_CHANGED;
for (int j = index.intValue()+1; j < numberOfCurves; j++) {
fillCurve(j, (DataCurve) _dataPlot.getElement(j));
}
break;
}
}
}
} else {
generateCurves(_dataPlot);
}
}
notifyListeners(new PlotEvent(Plot.this, type, index));
}
/**
* Generates all curves based on the specified data.
* In addition the legend view is created.
*/
private void generateCurves(DataPlot dataPlot) {
synchronized (_curves) {
_legendView = new GraphicalComposite(null);
_legendView.addElement(_legend.getBox());
_curves.setSize(0);
_nextCurveHints.setSize(0);
for (int i = 0, n = dataPlot.getNumberOfElements(); i < n; i++) {
Curve curve = _curveFactory.create(i, n, _clippingShape, _legend);
_curves.addElement(curve);
_nextCurveHints.addElement(new Vector());
DataCurve dataCurve = (DataCurve) dataPlot.getElement(i);
_legendView.addElement(curve.getLegendSymbol());
_legendView.addElement(
_legend.createCurveTitle(i, n, dataCurve.getTitle()));
fillCurve(i, dataCurve);
}
}
}
private void fillCurve(int curveIndex, DataCurve dataCurve) {
Vector curveHints = (Vector) _nextCurveHints.elementAt(curveIndex);
Curve curve = (Curve) _curves.elementAt(curveIndex);
curve.removeAllPoints();
for (int i = 0, n = dataCurve.getNumberOfElements(); i < n; i++) {
setHintForNextCurve(curveHints, i,
curve.addPoint(_transformation.transformToGraph(
(DataPoint) dataCurve.getElement(i)),
getHintForNextCurve(curveIndex - 1, i)));
}
}
private Hint getHintForNextCurve(int curveIndex, int pointIndex) {
Hint result = _initialHintForNextCurve;
if (curveIndex >= 0) {
Vector curveHints = (Vector) _nextCurveHints.elementAt(curveIndex);
result = pointIndex < curveHints.size() ?
(Hint) curveHints.elementAt(pointIndex)
: getHintForNextCurve(curveIndex - 1, pointIndex);
}
return result;
}
private void setHintForNextCurve(Vector curveHints, int pointIndex,
Hint hint) {
while (curveHints.size() <= pointIndex) {
curveHints.addElement(_initialHintForNextCurve);
}
curveHints.setElementAt(hint, pointIndex);
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
import jcckit.data.DataPlot;
import jcckit.graphic.Anchor;
import jcckit.graphic.ClippingRectangle;
import jcckit.util.ConfigParameters;
/**
* An abstract canvas containg a single {@link Plot}. The canvas is specified
* by a {@link ClippingRectangle}, called <em>paper</em>. A horizontal and
* vertical {@link Anchor} determine the position of the paper on the actual
* device.
*
* @author Franz-Josef Elmer
*/
public class PlotCanvas implements PlotListener {
/** Configuration parameter key. */
public static final String PAPER_KEY = "paper", HORIZONTAL_ANCHOR_KEY = "horizontalAnchor",
VERTICAL_ANCHOR_KEY = "verticalAnchor", PLOT_KEY = "plot";
private final ClippingRectangle _paper;
private final Anchor _horizontalAnchor;
private final Anchor _verticalAnchor;
private final Plot _plot;
/**
* Creates an instance from the specified configuration parameters. <table
* border=1 cellpadding=5>
* <tr>
* <th>Key &amp; Default Value</th>
* <th>Type</th>
* <th>Mandatory</th>
* <th>Description</th>
* </tr>
* <tr>
* <td><tt>horizontalAnchor = center</tt></td>
* <td><tt>String</tt></td>
* <td>no</td>
* <td>Horizontal position of the paper relative to the device border.
* Possible values are <tt>left</tt>, <tt>center</tt>, and
* <tt>right</tt>.</td>
* </tr>
* <tr>
* <td><tt>paper = 0,&nbsp;0,&nbsp;1,&nbsp;0.6</tt></td>
* <td><tt>double[]</tt></td>
* <td>no</td>
* <td>Rectangle defining the paper. The first two values determine the x-
* and y- coordinates (in device-independent units) of the lower-left
* corner. The last two values determine the upper-right corner.</td>
* </tr>
* <tr>
* <td><tt>plot = </tt>default values of {@link Plot}</td>
* <td><tt>ConfigParameters</tt></td>
* <td>no</td>
* <td>Definition of the {@link Plot}.</td>
* </tr>
* <tr>
* <td><tt>verticalAnchor = center</tt></td>
* <td><tt>String</tt></td>
* <td>no</td>
* <td>Vertical position of the paper relative to the device border.
* Possible values are <tt>top</tt>, <tt>center</tt>, and
* <tt>bottom</tt>.</td>
* </tr>
* </table>
* <p>
* Note, that this instance registers itself at the wrapped {@link Plot}
* instance.
*/
public PlotCanvas(ConfigParameters config) {
double[] paper = config.getDoubleArray(PAPER_KEY, new double[] { 0, 0, 1, 0.6 });
_paper = new ClippingRectangle(paper[0], paper[1], paper[2], paper[3]);
_horizontalAnchor = Anchor.getHorizontalAnchor(config, HORIZONTAL_ANCHOR_KEY, Anchor.CENTER);
_verticalAnchor = Anchor.getVerticalAnchor(config, VERTICAL_ANCHOR_KEY, Anchor.CENTER);
_plot = new Plot(config.getNode(PLOT_KEY));
_plot.addPlotListener(this);
}
/** Returns the paper definition. */
public ClippingRectangle getPaper() {
return _paper;
}
/** Returns the horizontal anchor. */
public Anchor getHorizontalAnchor() {
return _horizontalAnchor;
}
/** Returns the vertical anchor. */
public Anchor getVerticalAnchor() {
return _verticalAnchor;
}
/** Returns the plot. */
public Plot getPlot() {
return _plot;
}
/**
* Connects the wrapped {@link Plot} instance with the specified
* {@link DataPlot}.
*
* @param dataPlot
* Data to be connected with this plot canvas. Can be
* <tt>null</tt> in order to disconnect this instance from a
* <tt>DataPlot</tt>.
*/
public void connect(DataPlot dataPlot) {
_plot.connect(dataPlot);
}
/**
* Handles the spcified event. Here nothing is done. But subclass may
* override this method.
*/
public void plotChanged(PlotEvent event) {
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
/**
* A plot event signales some changes of a {@link Plot}.
* It has three attributes:
* <ul><li><b>source</b>: Indicates the <tt>Plot</tt> instance responsible
* for this event.
* <li><b>type</b>: The type of event.
* <li><b>message</b>: The message object. Its meaning depends on the
* type of event:
* <table border=1 cellpadding=5>
* <tr><th>Type</th><th>Meaning of the message object</th></tr>
* <tr><td>{@link PlotEventType#DATA_PLOT_CONNECTED},
* {@link PlotEventType#DATA_PLOT_DISCONNECTED}</td>
* <td>The {@link jcckit.data.DataPlot} (dis)connected with the
* {@link Plot} instance specified by the source.</td>
* <tr><td>{@link PlotEventType#DATA_PLOT_CHANGED}</td>
* <td>An <tt>Integer</tt> indicating the lowest index of
* those curves which have been changed.</td>
* <tr><td>{@link PlotEventType#DATA_CURVE_CHANGED}</td>
* <td>An <tt>Integer</tt> indicating the index of the curve
* which has been changed.</td>
* </table>
* </ul>
*
*
* @author Franz-Josef Elmer
*/
public class PlotEvent {
private final Plot _source;
private final PlotEventType _type;
private final Object _message;
/**
* Creates a new event for the specified source, type, and message.
* @param source Plot causing this event.
* @param type Type of the event. Possible values are
* {@link PlotEventType#DATA_PLOT_CHANGED},
* {@link PlotEventType#DATA_CURVE_CHANGED},
* {@link PlotEventType#DATA_PLOT_CONNECTED}, and
* {@link PlotEventType#DATA_PLOT_DISCONNECTED}.
* @param message Message object. Can be <tt>null</tt>
*/
public PlotEvent(Plot source, PlotEventType type, Object message) {
_source = source;
_type = type;
_message = message;
}
/** Returns the source of this event. */
public Plot getSource() {
return _source;
}
/**
* Returns the event type.
* @return either {@link PlotEventType#DATA_PLOT_CHANGED},
* {@link PlotEventType#DATA_CURVE_CHANGED},
* {@link PlotEventType#DATA_PLOT_CONNECTED}, or
* {@link PlotEventType#DATA_PLOT_DISCONNECTED}.
*/
public PlotEventType getType() {
return _type;
}
/** Returns the message object. */
public Object getMessage() {
return _message;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details
* (http://www.gnu.org/copyleft/lesser.html).
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcckit.plot;
/**
* Types of {@link PlotEvent PlotEvents}. Using the typesafe enumeration
* pattern.
*
* @author Franz-Josef Elmer
*/
public class PlotEventType {
/** Event type. */
public static final PlotEventType DATA_PLOT_CONNECTED = new PlotEventType(),
DATA_PLOT_DISCONNECTED = new PlotEventType(),
COODINATE_SYSTEM_CHANGED = new PlotEventType(),
DATA_CURVE_CHANGED = new PlotEventType(),
DATA_PLOT_CHANGED = new PlotEventType();
private PlotEventType() {}
}

Some files were not shown because too many files have changed in this diff Show More