samer@0: /* samer@0: * Copyright (c) 2000, Samer Abdallah, King's College London. samer@0: * All rights reserved. samer@0: * samer@0: * This software is provided AS iS and WITHOUT ANY WARRANTY; samer@0: * without even the implied warranty of MERCHANTABILITY or samer@0: * FITNESS FOR A PARTICULAR PURPOSE. samer@0: */ samer@0: samer@0: package samer.tools; samer@0: import samer.core.*; samer@0: import samer.core.util.*; samer@0: import java.awt.*; samer@0: samer@0: /** samer@0:

samer@0: A Plotter object is a Canvas with its own Graphics samer@0: (ie a GCanvas) which has two LinearMaps samer@0: to map a portion of 2d space to the window. samer@0: It can optionally draw the X and Y axes. samer@0: samer@0:

samer@0: The inner class Pen provides access to drawing samer@0: functions in the real-valued coordinate space. samer@0: */ samer@0: samer@0: public class Plotter extends samer.core.util.heavy.VCanvas samer@0: { samer@0: /** for passing to setAxes(int) */ samer@0: public static final int NEITHER = 0; samer@0: public static final int YAXIS = 2; samer@0: public static final int XAXIS = 1; samer@0: public static final int BOTH = 3; samer@0: public IMap xmap, ymap; samer@0: samer@0: int axesFlags=BOTH; samer@0: Color axesColor; samer@0: samer@0: /** samer@0: Default constructor makes Plotter with both axes samer@0: drawn, in grey. LinearMaps take their default samer@0: setup. samer@0: samer@0: @see samer.core.util.LinearMap samer@0: */ samer@0: samer@0: public Plotter() samer@0: { samer@0: xmap = new LinearMap(); samer@0: ymap = new LinearMap(); samer@0: samer@0: axesColor=Shell.getColor("axesColor",Color.gray); samer@0: } samer@0: samer@0: public void exposeMaps() { samer@0: exposeCommands(new VMap(xmap)); samer@0: exposeCommands(new VMap(ymap)); samer@0: } samer@0: samer@0: protected void sized() { samer@0: // make sure maps map to whole window samer@0: xmap.setIntRange(width); samer@0: ymap.setIntRange(height); samer@0: } samer@0: samer@0: public Dimension getPreferredSize() { return new Dimension(256,256); } samer@0: samer@0: /** use one of NONE, XAXIS, YAXIS or BOTH */ samer@0: public void setAxes(int f) { axesFlags=f; } samer@0: public void setAxesColor(Color c) { axesColor=c; } samer@0: samer@0: /** not really for public use */ samer@0: public void drawAxes(Graphics g) samer@0: { samer@0: if (axesFlags!=0) { samer@0: g.setColor(axesColor); samer@0: samer@0: if ((axesFlags&XAXIS)!=0) { samer@0: int oy = height-ymap.toInt(0.0); samer@0: if (!ymap.wasClipped()) g.drawLine(0,oy,width-1,oy); samer@0: } samer@0: samer@0: if ((axesFlags&YAXIS)!=0) { samer@0: int ox = xmap.toInt(0.0); samer@0: if (!xmap.wasClipped()) g.drawLine(ox,0,ox,height-1); samer@0: } samer@0: samer@0: g.setColor(getForeground()); samer@0: } samer@0: } samer@0: samer@0: public void clear(Graphics g) { super.clear(g); drawAxes(g); } samer@0: public void paint(Graphics g) { Tools.initGraphics(g); drawAxes(g); } samer@0: samer@0: /** return a new Pen for this Plotter */ samer@0: public Pen getPen() { return new Pen(graphics); } samer@0: public Pen getPen(Graphics g) { return new Pen(g); } samer@0: samer@0: // ----- drawing tools ------- samer@0: samer@0: /** samer@0:

samer@0: This class provides a state based plotting samer@0: environment (slightly inspired by Postscripts samer@0: crazy reverse polish). There is are two samer@0: current points: one for general use and another samer@0: which serves as the other point for lines, samer@0: rectangles and ellipses. samer@0: samer@0:

samer@0: The primary current point can be set in absolute samer@0: coordinates using abs(x,y) or samer@0: moved relatively using rel(dx,dy). samer@0: The folling functions use only the primary samer@0: current point: samer@0:

samer@0: Calling move() copies the the location samer@0: of the primary current point to the secondary samer@0: current point. The following functions use both samer@0: the current points: samer@0: samer@0:

samer@0: Some of the rectangle drawing commands samer@0: only work if the primary current point is below samer@0: and to the right (on the screen, that is) of the samer@0: secondary. The rectify function samer@0: swaps coordinates between the two points to make samer@0: it so. It will screw up any line drawing though. samer@0: samer@0: */ samer@0: samer@0: public class Pen samer@0: { samer@0: Graphics graphics; samer@0: public Point p0=new Point(); // secondary samer@0: public Point p1=new Point(); // primary samer@0: vec2 r=new vec2(0,0); samer@0: int moff=3, msz=moff*2; // marker size samer@0: int ix, iy; // blob image hotspot samer@0: Color col=getForeground(); // current colour samer@0: Image blobImg; // drawn by blob() samer@0: samer@0: /** samer@0:

samer@0: A program can have several Pens, each with different samer@0: colours, but the underlying Graphics has a samer@0: single current Color. So, if you have multiple Pens, samer@0: each pen must activate() its colour samer@0: before drawing anything. samer@0: */ samer@0: samer@0: public Pen(Graphics g) { graphics=g; } samer@0: final public Pen activate() { graphics.setColor(col); return this; } samer@0: final public Pen setColor(Color c) { col=c; graphics.setColor(c); return this; } samer@0: final public void setMarkerSize(int r) { moff=r; msz=2*r; } samer@0: samer@0: /** returns true if the last primary current point was clipped */ samer@0: public final boolean clipped() { return xmap.wasClipped() | ymap.wasClipped(); } samer@0: public final Pen abs( double x, double y) { r.x=x; r.y=y; map(); return this; } samer@0: public final Pen rel( double x, double y) { r.x+=x; r.y+=y; map(); return this; } samer@0: samer@0: /** copies position of given pen to primary current point */ samer@0: public final Pen set( Pen p) { r.x=p.r.x; r.y=p.r.y; p1.x=p.p1.x; p1.y=p.p1.y; return this; } samer@0: public final Pen move() { p0.x=p1.x; p0.y=p1.y; return this; } samer@0: public final Pen line() { samer@0: graphics.drawLine(p0.x,p0.y,p1.x,p1.y); samer@0: return this; samer@0: } samer@0: samer@0: public Pen rect() { samer@0: graphics.drawRect(p0.x,p0.y,p1.x-p0.x,p1.y-p0.y); samer@0: return this; samer@0: } samer@0: samer@0: public Pen fillRect() { samer@0: graphics.fillRect(p0.x,p0.y,p1.x-p0.x,p1.y-p0.y); samer@0: return this; samer@0: } samer@0: samer@0: /** this swaps xs and ys to make sure rectangles work */ samer@0: public Pen rectify() { samer@0: if (p1.y32000) p1.y=-32000; samer@0: else p1.y=height-p1.y; samer@0: } else p1.y=height-p1.y; samer@0: return this; samer@0: } samer@0: samer@0: /* These last three functions are here to speed up samer@0: two common operations: samer@0: samer@0: moveto(x,y) is exactly equivalent to abs(x,y).map().move(); samer@0: lineto(x,y) is exactly equivalent to abs(x,y).map().line().move(); samer@0: lineto(x,y) is exactly equivalent to line().move() ; samer@0: */ samer@0: samer@0: public final Pen lineto() samer@0: { samer@0: graphics.drawLine(p0.x,p0.y,p1.x,p1.y); samer@0: p0.x=p1.x; p0.y=p1.y; samer@0: return this; samer@0: } samer@0: samer@0: public final Pen lineto(double x, double y) samer@0: { samer@0: r.x=x; r.y=y; map(); samer@0: graphics.drawLine(p0.x,p0.y,p1.x,p1.y); samer@0: p0.x=p1.x; p0.y=p1.y; samer@0: return this; samer@0: } samer@0: samer@0: // this is exactly equivalent to abs(x,y).map().move(); samer@0: public final Pen moveto(double x, double y) samer@0: { samer@0: r.x=x; r.y=y; map(); samer@0: p0.x=p1.x; p0.y=p1.y; samer@0: return this; samer@0: } samer@0: samer@0: /** draws the current blob image at the current point */ samer@0: public Pen blob() { samer@0: graphics.drawImage(blobImg,ix+p1.x,iy+p1.y,null); samer@0: return this; samer@0: } samer@0: samer@0: /** sets the blob image to be pixel of a given colour */ samer@0: public void setBlobColor(Color col) { samer@0: blobImg=createPixel(col); samer@0: } samer@0: samer@0: /** sets blob image to given image */ samer@0: public void setBlobImage(Image i) { blobImg=i; ix=0; iy=0; } samer@0: samer@0: /** sets blob image with given hotspot */ samer@0: public void setBlobImage(Image i, int xhot, int yhot) { blobImg=i; ix=-xhot; iy=-yhot; } samer@0: } samer@0: } samer@0: