• Post Reply
  • Bookmark Topic Watch Topic
  • New Topic

Wanted: How to use Strategy Pattern with Shapes?

Siegfried Heintze
Ranch Hand
Posts: 403
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have three questions
(1) How do I refactor Craig's code to use Strategy pattern so I can do
for(Myshape shape : Shapes) shape.draw(g2);
(2) How do I create a class called TextShape so I can use the above loop with shapes that contain text strings.

Craig used "g2.draw(at.createTransformedShape(shapes[0]));" and I cannot figure out how to refactor this.

When I pass an instance of TextShape to createTransformedShape I don't get an instace of TextShape back which I can subsequently draw using its member function.

We had previously tried to just apply the transform to the g2 instead of shape but that did not pan correctly.

(3) How do I implement hit testing? See my attempt below.


import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.font.*;
import java.awt.geom.*;

import javax.swing.*;
import javax.swing.event.*;

* See http://www.iam.ubc.ca/guides/javatut99/uiswing/start/swingApplet.html
* http://java.sun.com/j2se/1.5.0/docs/guide/plugin/developer_guide/using_tags.html
* From Craig Wood http://www.coderanch.com/forums/
* $Log: GraphicsOnly.java,v $
* Revision 1.8 2006/10/28 03:34:09 siegfried
* use shape array.
* Revision 1.7 2006/10/27 20:37:54 siegfried
* Make coordinate system consistent for shapes and text.
* Revision 1.6 2006/10/27 18:40:45 siegfried
* Multiple instances of rotated text work!
* To do: Create descendant of class shape to hold text.
* Revision 1.5 2006/10/19 03:23:50 siegfried
* Add rotated text that should zoom and pan well.
* Revision 1.4 2006/10/19 03:10:39 siegfried
* update
* Revision 1.3 2006/10/08 23:33:24 siegfried
* Use Craig Wood's code to implement applet. http://www.coderanch.com/t/258528/Applets/java/null-Pointer-Exception-applet-viewer
* Revision 1.2 2006/10/07 21:14:45 siegfried
* Add applet compatibility.
* Revision 1.1 2006/10/07 19:54:31 siegfried
* initial from Craig Wood
* Begin commands to execute this file using Java with bash
* export CLASSPATH=.
* javac -g GraphicsOnly.java
* # javac -g PanAndZoom.java
* cat <<EOF >GraphicsOnlyTmp.html
* <!--
* cd c:/Program Files/Java/jdk1.5.0_04/lib/htmlconverter.jar
* ../bin/java -jar htmlconverter.jar -gui
* -->
* <HTML>
* <HEAD>
* <TITLE>Pan And Zoom</TITLE>
* <BODY>
* <object
* classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
* codebase = "http://java.sun.com/update/1.5.0/jinstall-1_5-windows-i586.cab#Version=5,0,0,5"
* WIDTH = 1000 HEIGHT = 800 >
* <PARAM NAME = CODE VALUE = "GraphicsOnly.class" >
* <param name = "type" value = "application/x-java-applet;version=1.5">
* <param name = "scriptable" value = "false">
* <comment>
* <embed
* type = "application/x-java-applet;version=1.5" \
* CODE = "GraphicsOnly.class" \
* WIDTH = 1000 \
* HEIGHT = 800
* scriptable = false
* pluginspage = "http://java.sun.com/products/plugin/index.html#download">
* <noembed>
* <B>Error! You must use a Java enabled browser.</B>
* </noembed>
* </embed>
* </comment>
* </object>
* <!--
* <APPLET CODE = "GraphicsOnly.class" WIDTH = 500 HEIGHT = 400>
* <B>Error! You must use a Java enabled browser.</B>
* -->
* </BODY>
* </HTML>
* #appletviewer GraphicsOnlyTmp.html
* /c/Program\ Files/Mozilla\ Firefox/firefox.exe GraphicsOnlyTmp.html
* rm GraphicsOnlyTmp.html
* # IEXPLORE file://PanAndZoom.html
* #java GraphicsOnly
* # rm GraphicsOnly.class
* End commands to execute this file using Java with bash

// public
class InnerGraphicsOnly extends JPanel {
Shape[] shapes;
RenderingHints hints;
Dimension m_dimOriginalWindow;
double scale = 1.0;
static final double pi = 4*Math.atan2(1,1);
private int m_nWidthWindow;
private int m_nHeightWindow;

public InnerGraphicsOnly() {
hints = new RenderingHints(null);
m_dimOriginalWindow = new Dimension(10,10);
setBackground(new Color(240,200,200));
addMouseListener(new MyMouseListener());
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
if(shapes == null) initShapes();
m_nWidthWindow = getWidth();
m_nHeightWindow = getHeight();
// Keep shapes centered on panel.
AffineTransform at = makeCenteredAffineTransform();
at.scale(scale, scale);
g2.setPaint(new Color(240,240,200));
Shape shape=at.createTransformedShape(shapes[3]);
TextShape text=((TextShape)shapes[3]);
// Draw some text.
drawString(g2, "Hello World", m_dimOriginalWindow.getWidth()/4, m_dimOriginalWindow.getHeight()/4);
drawString(g2, "Sieglinde", m_dimOriginalWindow.getWidth()/8, m_dimOriginalWindow.getHeight()/8);
* @return
private AffineTransform makeCenteredAffineTransform() {
double x = (getWidth() - scale*m_dimOriginalWindow.width)/2;
double y = (getHeight() - scale*m_dimOriginalWindow.height)/2;
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
return at;

* @param g2
* @param at
* @param s
private void drawString(Graphics2D g2, String s, double xxx, double yyy) {
double x = scale*(xxx-m_dimOriginalWindow.width/2)+m_nWidthWindow/2;
double y = scale*(yyy-m_dimOriginalWindow.height/2)+m_nHeightWindow/2;
double xx = x, yy= y;
Font font = g2.getFont().deriveFont(18f);
Dimension str = getStringExtent(g2, s, font);
//x = x - scale*str.getWidth()/2;
//y = y + scale*str.getHeight()/2;
// We have finished using at above for the spatial transform
// work so we can refit/resue it for the text rendering.
AffineTransform at = AffineTransform.getTranslateInstance(x - scale*str.getWidth()/2, y + scale*str.getHeight()/2);//at.setToTranslation(x, y);
// Scale text in place (we have already adjusted the origin
// for the scaling portion).
at.scale(scale, scale);
// Rotate text to align with sw - ne diagonal.
// Determine the angle of rotation.
Rectangle2D bounds = shapes[0].getBounds2D();
double dy = -bounds.getHeight(); // looking up (-) toward this origin
double dx = bounds.getWidth();
double theta = /*Math.atan2(dy, dx);//*/(Math.atan2(dy, dx)+(scale-1)*2)%(2*pi);
// In java, positive angles are measured clockwise from 3 o'clock
// except in arc measure which is reversed.
//System.out.printf("theta = %.1f%n", Math.toDegrees(theta));
// Make up another transform that rotates theta radians about
// the center of the text bounding rectangle. Multiply this
// new transform by the translation/scaling transform.
// Since the text is centered on this component we can use
// its center coordinates for the rotation origin.
// Otherwise, we would locate the center of rotation as
//x += scale*widthString/2;
//y = y + scale*descentString - scale*str.getHeight()/2;
System.out.printf("x = %.1f\ty = %.1f\tw/2 = %d\th/2 = %d%n", x, y, (int)xx, (int)yy);
AffineTransform xfRotate = AffineTransform.getRotateInstance(theta, xx, yy);
xfRotate.concatenate(at); // The order is important (matrix math).
// Since we did all the work in the transform, draw text at (0,0)
g2.drawString(s, 0, 0);
g2.setFont(font); // restore

* @param g2
* @param s
* @param font
* @return
protected Dimension getStringExtent(Graphics2D g2, String s, Font font) {
FontRenderContext frc = g2.getFontRenderContext();
float widthString = (float)font.getStringBounds(s, frc).getWidth();
LineMetrics lm = font.getLineMetrics(s, frc);
Dimension str = new Dimension();
str.setSize(widthString, lm.getHeight()+ lm.getDescent());
return str;

public Dimension getPreferredSize() {
int w = (int)(scale*m_dimOriginalWindow.width);
int h = (int)(scale*m_dimOriginalWindow.height);
return new Dimension(w, h);

private void initShapes() {
int w = getWidth();
int h = getHeight();
shapes = new Shape[] {
new Rectangle2D.Double(w / 16, h / 16, w * 7 / 8, h * 7 / 8),
new Line2D.Double(w / 16, h * 15 / 16, w * 15 / 16, h / 16),
new Ellipse2D.Double(w / 4, h / 4, w / 2, h / 2),
new TextShape(w / 16, h * 15 / 16,""+w/16+","+h*15/16,18f)
m_dimOriginalWindow.width = w;
m_dimOriginalWindow.height = h;

public JSlider getControl() {
JSlider slider = new JSlider(JSlider.HORIZONTAL, 5, 1000, 100);
slider.addChangeListener(new SliderChangeListener());
return slider;

public static void main(String[] args) {
InnerGraphicsOnly app = new InnerGraphicsOnly();
JFrame f = new JFrame();
f.getContentPane().add(new JScrollPane(app));
f.getContentPane().add(app.getControl(), "Last");
f.setSize(400, 400);
class SliderChangeListener implements ChangeListener{
public void stateChanged(ChangeEvent e) {
int value = ((JSlider)e.getSource()).getValue();
scale = value/100.0;
class MyMouseListener implements MouseListener{
public void mouseClicked(MouseEvent e) {
public void mousePressed(MouseEvent e) {
AffineTransform at;
try {
at = makeCenteredAffineTransform();//.createInverse();

Graphics2D g2 = (Graphics2D) getGraphics();
Point2D out = new Point();
out = at.transform(e.getPoint(), out);
System.out.print("" + out.getX() + "," + out.getY());
for (Shape shape : shapes) {
if (g2.hit(new Rectangle((int) out.getX(),
(int) out.getY(), 10, 10), shape, true))
} catch (Throwable e1) {
// TODO Auto-generated catch block
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}

interface IDrawable{
public void draw(Graphics2D g);
class TextShape extends Rectangle2D.Double implements IDrawable{
protected String m_sText;
public TextShape(double x, double y, String s, float fFontSize){
m_sText = s;
Graphics2D g2 = (Graphics2D)getGraphics();
Font font = g2.getFont().deriveFont(fFontSize);
Dimension d = getStringExtent(g2,s,font);
public void draw(Graphics2D g){
public class GraphicsOnly extends JApplet {
public void init() {
InnerGraphicsOnly graphicsOnly = new InnerGraphicsOnly();
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add(new JScrollPane(graphicsOnly));
cp.add(graphicsOnly.getControl(), "Last");

public static void main(String[] args) {
JApplet applet = new GraphicsOnly();
JFrame f = new JFrame();
// Since there is no AppletContext we have to
// call the required methods in the applet.
Henry Wong
Posts: 20881
C++ Chrome Eclipse IDE Firefox Browser Java jQuery Linux VI Editor Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Not exactly sure where this topic should go, but it looks like it is probably more related to Swing than Applets or Process. It definitely doesn't belong in the "Other API" forum.

Moving this to the Swing forum...

Ken Blair
Ranch Hand
Posts: 1078
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This is an idea case for strategy and I've done this before with Swing too. All you need to implement strategy is a class or interface, preferably interface for reasons that should be apparent, and as many implementations as necessary. It's as simple as something like this:

The reason for providing the width and height is because it's expected that a Drawable might draw differently depending on the size of the area. There's no need for an x or y because the origin of the graphics context can be translated before we pass it. If this is being used in Swing then it's a reasonable assumption that we're going to have a Graphics2D and a lot of things you might want a Drawable to do can't be done on a Graphics.

It would then be easy to create a class DrawableShape that encapsulates everything need to draw a Shape. Alternatively, DrawableShape could be an interface though there is likely little point to doing so. A simple example:

In my work I actually use a "PaintStrategy" with a "paint" method that is identical. I use it to make arbitrary painting easy. Implementations I've created range from painting images in a myriad of ways to filling with arbitrary Paint instances to painting animations. Furthermore I've used the strategy with everything from custom JComponents to drawing on an image. I would also note that I recently discovered some of the Sun people working on swingx at java.net are doing almost the exact same thing but they call it a "Painter".

As for creating a shape that draws text: that's just silly. A Shape and text are different things. Instead I would create a Drawable implementation that can draw text. If you write your code to accept any Drawable then you're not limited to Shapes to begin with and there's no need to begin with.
Siegfried Heintze
Ranch Hand
Posts: 403
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks but I'm still confused. I believe we are in agreement that I create descendant of class Shape called TextShape that will draw text.

When I do that, I have trouble making the following statement work:


For starters, draw is a member of Graphics, not shape. Secondly, how do I create my TextShape class so that at.CreateTransformedShape does the right thing?

How does at.CreateTransformedShape work? Is it going to modify shapes[0] or create a copy of that instance and return it. I think it is creating a copy because if it is modifying the one original copy, the scaling would be compounded every time I resize the window and that is not happening. Therefor it must be making a copy. But how does it know how to make a copy that includes the extra data members unique to my descendant class?

Also, I would still love some help with hit testing.

Ken Blair
Ranch Hand
Posts: 1078
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Siegfried Heintze:
Thanks but I'm still confused. I believe we are in agreement that I create descendant of class Shape called TextShape that will draw text.

No, there's no reason to. If you want to use strategy for painting then "Shape" is not the strategy. An implementation of strategy would be capable of painting arbitrary shapes. Consequently there's no reason to create a version of Shape that draws text which doesn't make any sense in the first place. You would not need to use createTransformedShape() to begin with because the implementation that draws text would be capable of calculating the proper width and height. Here's a simple example that would do the drawing you're doing:

  • Post Reply
  • Bookmark Topic Watch Topic
  • New Topic