lightgraph.Graph Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of light-weight-graphing Show documentation
Show all versions of light-weight-graphing Show documentation
Library for creating graphs in a swing environment. Can produce png or svg.
The newest version!
/*
*
*/
package lightgraph;
import lightgraph.gui.GraphFrame;
import lightgraph.painters.GraphPainter;
import lightgraph.painters.PanelPainter;
import lightgraph.painters.SvgPainter;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.awt.image.BufferedImage;
import java.awt.geom.Point2D;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import java.awt.FontMetrics;
/**
* A lightweight graphing application with options for formating a graph.
*
* @author mbs207
*/
public class Graph {
public boolean AUTOX;
public boolean AUTOY;
boolean XLABEL;
boolean YLABEL;
boolean GRID;
boolean XTICS;
int XTIC_COUNT;
boolean YTICS;
int YTIC_COUNT;
boolean TITLE;
public double MINX;
public double MINY;
public double MAXX;
public double MAXY;
double PADDING = 10;
double YTICS_WIDTH = 30;
double XTICS_HEIGHT = 20;
double TITLE_HEIGHT= 0;
double FONT_HEIGHT = 12;
double LEFT_MARGIN, RIGHT_MARGIN, TOP_MARGIN, BOTTOM_MARGIN;
FontMetrics FONT_METRICS;
/** indicates a pending scale */
boolean SCALE;
/**canvas height and width*/
public int CHEIGHT;
public int CWIDTH;
double KEY_X, KEY_Y;
boolean KEY_POSITION_SET=false;
Color AXIS_COLOR, BACKGROUND;
BufferedImage img;
public ArrayList DATASETS;
String[] xtics, ytics;
String xlabel, ylabel, title;
public GraphPanel panel;
GraphMutex IMAGE_LOCK = new GraphMutex();
private JFrame frame;
private LGFont ticFont = new LGFont("Liberation", Font.PLAIN, 10);
private LGFont titleFont = new LGFont("Liberation", Font.PLAIN, 14);
private LGFont labelFont = new LGFont("Liberation", Font.PLAIN, 12);
public Graph(){
MINX = Double.MAX_VALUE;
MINY = Double.MAX_VALUE;
MAXX = -Double.MAX_VALUE;
MAXY = -Double.MAX_VALUE;
AUTOX = true;
AUTOY = true;
CHEIGHT = 480;
CWIDTH = 640;
KEY_X = 100;
KEY_Y = 45;
XTICS = true;
XTIC_COUNT = 7;
YTICS = true;
YTIC_COUNT = 5;
img = new BufferedImage(CWIDTH,CHEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
FONT_METRICS = g.getFontMetrics();
DATASETS = new ArrayList();
SCALE=false;
AXIS_COLOR = Color.BLACK;
BACKGROUND = Color.WHITE;
}
public Graph(double[] x, double[] y){
this();
addData(x,y);
}
/**
* Creates a new DataSite, with color, line and point information that
* will be drawn.
*
* @param x array of x values
* @param y array of y values
* @return the dataset created
*/
public DataSet addData(double[] x, double[] y){
DataSet d = new DataSet(x,y);
d.setColor(GraphDefaults.getDefaultColor(DATASETS.size()));
DATASETS.add(d);
SCALE = true;
return d;
}
/**
* replaces the data set with a new data set consisting of the xy pairs.
*
* @param set
* @param x
* @param y
* @return
*/
public DataSet replaceData(int set, double[] x, double[] y){
DataSet d = new DataSet(x,y);
DataSet old = DATASETS.get(set);
d.setColor(old.COLOR);
d.setPoints(old.POINTS);
d.setLine(old.LINE);
d.setLabel(old.label);
DATASETS.remove(set);
if(setmax) max = width;
}
}
//8 for some space, 50 for the line, RIGHT_MARGIN to actually be in the graph.
KEY_X = max + 8 + 50 + RIGHT_MARGIN;
}
for(DataSet set: DATASETS){
if(set.label!=null){
double top = KEY_Y + FONT_HEIGHT*1.4*count;
double left = CWIDTH - KEY_X;
p.setColor(AXIS_COLOR);
p.setFont(labelFont);
p.drawString(set.label, left + 50, top);
p.setColor(set.COLOR);
ArrayList pts = new ArrayList();
double mark_y = top - FONT_HEIGHT*0.4;
pts.add(new Point2D.Double(left + 5, mark_y));
pts.add(new Point2D.Double(left + 45, mark_y));
Point2D middle = new Point2D.Double(left+25, mark_y);
if(set.LINE!=null){
set.LINE.drawLine(pts, p);
}
if(set.POINTS!=null){
set.POINTS.drawPoint(middle,p);
}
count++;
}
}
p.endGroup();
}
/**
* Takes the full y range and breaks it into 5 tics, uses the transform to place points.
* @param p
* @param t
*/
public void drawYTics(GraphPainter p, AffineTransform t){
double delta = (MAXY - MINY)/(YTIC_COUNT-1);
int max = 0;
for(String tic: ytics){
max = tic.length()>max?tic.length():max;
}
p.startGroup();
for(int i = 0; itl.length()?max:tl.length();
}
p.endGroup();
String format = "%s";
p.startGroup();
for(int i = 0; i pts = new ArrayList();
for(Point2D pt: set.DATA){
scaled = new Point2D.Double();
pts.add(transform.transform(pt,scaled));
}
painter.setColor(set.COLOR);
if(set.LINE!=null){
set.LINE.drawLine(pts, painter);
}
if(set.ERRORS!=null){
if(set.POINTS!=null){
set.ERRORS.setWeight(set.POINTS.WEIGHT);
set.ERRORS.setSize(set.POINTS.SIZE);
} else if(set.LINE!=null){
set.ERRORS.setWeight(set.LINE.WIDTH);
}
painter.startGroup();
double scale_x = transform.getScaleX();
double scale_y = transform.getScaleY();
set.ERRORS.setScale(scale_x, scale_y);
for(int i = 0; ix_overflow?now_width:x_overflow;
xtics[i] = value;
}
RIGHT_MARGIN += x_overflow;
}
if(YTICS){
double delta = (MAXY - MINY)/(YTIC_COUNT-1);
ytics = new String[YTIC_COUNT];
int ytics_width = 0;
for(int i = 0; inow_width?ytics_width:now_width;
ytics[i] = value;
}
YTICS_WIDTH = ytics_width;
//set in 'reset function.
//LEFT_MARGIN += YTICS_WIDTH;
}
}
/**
* Sets the autoscale in the x directions.
*
*/
public void autoScaleX(){
if(!AUTOX)
SCALE=true;
AUTOX = true;
}
/**
* Sets the autoscale for the y-axis
*
*/
public void autoScaleY(){
if(!AUTOY)
SCALE=true;
AUTOY = true;
}
/**
* Goes through all of the current data and find the min/max for scaling the image.
*
*/
public void autoScale(){
if(AUTOX||AUTOY){
double mnx = Double.MAX_VALUE;
double mny = Double.MAX_VALUE;
double mxx = -Double.MAX_VALUE;
double mxy = -Double.MAX_VALUE;
for(DataSet set: DATASETS){
for(Point2D pt: set.DATA){
if(checkX(pt.getX())&&checkY(pt.getY())){
mnx = pt.getX()mxx?pt.getX():mxx;
mxy = pt.getY()>mxy?pt.getY():mxy;
}
}
}
MAXX = AUTOX?mxx:MAXX;
MINX = AUTOX?mnx:MINX;
if(AUTOX&&MAXX==MINX){
//System.out.println("Warner: X-Range is zero rescaline");
MAXX++;
MINX--;
}
MAXY = AUTOY?mxy:MAXY;
MINY = AUTOY?mny:MINY;
if(AUTOY&&MAXY==MINY){
//System.out.println("Warner: Y-Range is zero rescaline");
MAXY++;
MINY--;
}
}
SCALE=false;
}
/**
* Checks if x value is valid to use as a point for autoscaling.
* @param x value along x axis
* @return whether x is in range or it is autoscaling.
*/
boolean checkX(double x){
return AUTOX||(x>=MINX&&x<=MAXX);
}
/**
* Checks if y value is value to use as a point for autoscaling
*
* @param y position
* @return either y in range or it is autoscaling.
*/
boolean checkY(double y){
return AUTOY||(y>=MINY&&y<=MAXY);
}
/**
* Set the xrange values the minimum must be less than the maximum.
*
* @param min min x value
* @param max max y value
*/
public void setXRange(double min, double max) throws IllegalArgumentException {
if(min>=max)
throw new IllegalArgumentException("the minimum must be less than the maximum");
AUTOX = false;
MINX = min;
MAXX = max;
SCALE=true;
}
/**
* Set the xrange values the minimum must be less than the maximum.
*
* @param min min x value
* @param max max y value
*/
public void setYRange(double min, double max) throws IllegalArgumentException {
if(min>=max)
throw new IllegalArgumentException("the minimum must be less than the maximum");
AUTOY = false;
MAXY = max;
MINY = min;
}
/**
* Gets the data set at the corresponding index i.
*
* @param i index of selected dataset
* @return the line corresponding.
*/
public DataSet getDataSet(int i){
return DATASETS.get(i);
}
public int dataSetCount(){
return DATASETS.size();
}
/**
* Append a data point to an existing data set.
*
* @param set
* @param x
* @param y
*/
public void appendPoint(int set, double x, double y){
DATASETS.get(set).addPoint(x,y);
SCALE = true;
}
public void setContentSize(int width, int height){
if(XTICS||YTICS){
createTics();
}
if(XTICS){
width += YTICS_WIDTH;
}
if(YTICS){
height += XTICS_HEIGHT;
}
if(CWIDTH==width || CHEIGHT==height)
return;
CWIDTH = width;
CHEIGHT = height;
SCALE = true;
}
/**
* Shows the graph in its own JFrame
*
*/
public void show(){
show(true);
}
public void show(boolean exit_on_close, String window_title){
if(frame!=null){
frame.setVisible(true);
return;
}
GraphFrame y = new GraphFrame(window_title);
//y.setSize(640,480);
if(exit_on_close)
y.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
resetGraph();
panel = new GraphPanel(img);
y.setGraph(this);
y.pack();
y.setVisible(true);
frame = y;
}
public void show(boolean exit_on_close){
show(exit_on_close, "Graph Panel");
}
public void repaint(){
IMAGE_LOCK.get();
try{
panel.updateImage(img);
} catch(NullPointerException e){
System.out.println("unable to repaint");
e.printStackTrace();
}
IMAGE_LOCK.release();
}
/**
* This does all of the reset/repaint functions. Additionaly
* it will request a rescale.
*
*
* @param rescale if the auto scale is performed.
*/
public void refresh(boolean rescale){
SCALE = rescale||SCALE;
resetGraph();
repaint();
}
public void setYTicCount(int c){
if(c==0){
YTICS=false;
} else{
YTICS=true;
}
YTIC_COUNT = c;
}
public void setXTicCount(int c){
XTIC_COUNT = c;
if(c==0){
XTICS=false;
} else{
XTICS=true;
}
}
public int getXTicCount(){
return XTIC_COUNT;
}
public int getYTicCount(){
return YTIC_COUNT;
}
public void setBackground(Color c){
BACKGROUND = c;
}
public void setAxisColor(Color c){
AXIS_COLOR=c;
}
public Color getBackground(){
return BACKGROUND;
}
public String getXLabel(){
return xlabel;
}
public String getYLabel(){
return ylabel;
}
public String getTitle(){
return title;
}
/**
* Sets the distance from the right edge of the graph for the top left
* cornder of the key.
*
* @param x distance from right edge of graph.
*/
public void setKeyX(double x){
KEY_X = x;
KEY_POSITION_SET=true;
}
/**
* Sets the distance from the top of the graph to the top of the key.
* @param y
*/
public void setKeyY(double y){
KEY_Y = y;
}
public double getKeyX(){
return KEY_X;
}
public double getKeyY(){
return KEY_Y;
}
public GraphPanel getGraphPanel() {
if (panel == null) {
panel=new GraphPanel(img);
}
return panel;
}
/**
* Based on the coordinates of the panel, gets the coordinates in data space.
*
* @param panel_x x position on the panel.
* @param panel_y y position on the panel.
* @return the position in data space.
*/
public double[] getDataCoordinates(double panel_x, double panel_y){
//the current position of the click in data space scaled to pixels.
double data_x = panel_x - LEFT_MARGIN;
double data_y = CHEIGHT - BOTTOM_MARGIN - panel_y;
double real_x = MINX + (MAXX - MINX)*data_x/(CWIDTH-LEFT_MARGIN-RIGHT_MARGIN);
double real_y = MINY + (MAXY - MINY)*data_y/(CHEIGHT-BOTTOM_MARGIN-TOP_MARGIN);
return new double[]{real_x, real_y};
}
/**
* Takes a coordinate in the data/real space and returns a coodinate in image space.
* @param real_x value of x in data space.
* @param real_y value of y in data space.
* @return {x,y} in px in image space. The image is the buffered image used to draw on a panel.
*/
public double[] getImageCoordinates(double real_x, double real_y){
double panel_x = LEFT_MARGIN + (real_x - MINX)*(CWIDTH-LEFT_MARGIN-RIGHT_MARGIN)/(MAXX - MINX);
double panel_y = CHEIGHT - BOTTOM_MARGIN - (real_y - MINY)*(CHEIGHT - BOTTOM_MARGIN - TOP_MARGIN)/(MAXY - MINY);
return new double[]{panel_x, panel_y};
}
public void setTitleFont(LGFont font) {
titleFont = font;
}
public void setLabelFont(LGFont labelFont) {
this.labelFont = labelFont;
}
public void setTicFont(LGFont ticFont) {
this.ticFont = ticFont;
}
}
class GraphMutex{
boolean HELD = false;
synchronized public void get(){
while(HELD){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
HELD = true;
}
synchronized public void release(){
HELD=false;
notifyAll();
}
}