8

I'm using Java AWT to draw lines on a panel (Line2D and Graphics2D.drawLine()) and I'm wondering how I can draw a line with tick marks, similar to:

|----|----|----|----|----|

I know the positions I'd like to draw the ticks at in advance.

The lines could be in any position, so the ticks must be drawn at an angle releative to the line itself.

My basic geometry & ability to apply it in Java is failing me. :)

aioobe
  • 413,195
  • 112
  • 811
  • 826
Matt
  • 177
  • 2
  • 7
  • Do you want to draw the "ruler" lines in arbitrary directions? Are the ticks at fixed intervals, or do you want them to appear, at say 0%,33%, 66%, 100% ? – aioobe Aug 15 '10 at 17:32
  • Please see below for description of the graphics in general. The tick marks will be evenly spaced with arbitrary spacing. (IE: I need a solution where I can segment a line and draw an arbitrary number of these ticks along its length at each segment) – Matt Aug 15 '10 at 17:40
  • let me know what you think of my answer. – jjnguy Aug 15 '10 at 19:00

3 Answers3

12

I suggest you

  1. implement a ruler-drawing-method that draws a simple horizontal ruler from left to right
  2. Figure out the desired angle using Math.atan2.
  3. Apply an AffineTransform with translation and rotation before invoking the ruler-drawing-method.

Here is a complete test-program. (The Graphics.create method is used to create a copy of the original graphics object, so we don't mess up the original transform.)

import java.awt.*;

public class RulerExample {

    public static void main(String args[]) {
        JFrame f = new JFrame();
        f.add(new JComponent() {

            private final double TICK_DIST = 20;

            void drawRuler(Graphics g1, int x1, int y1, int x2, int y2) {
                Graphics2D g = (Graphics2D) g1.create();

                double dx = x2 - x1, dy = y2 - y1;
                double len = Math.sqrt(dx*dx + dy*dy);
                AffineTransform at = AffineTransform.getTranslateInstance(x1, y1);
                at.concatenate(AffineTransform.getRotateInstance(Math.atan2(dy, dx)));
                g.transform(at);

                // Draw horizontal ruler starting in (0, 0)
                g.drawLine(0, 0, (int) len, 0);
                for (double i = 0; i < len; i += TICK_DIST)
                    g.drawLine((int) i, -3, (int) i, 3);
            }

            public void paintComponent(Graphics g) {
                drawRuler(g, 10, 30, 300, 150);
                drawRuler(g, 300, 150, 100, 100);
                drawRuler(g, 100, 100, 120, 350);
                drawRuler(g, 50, 350, 350, 50);
            }
        });

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(400, 400);
        f.setVisible(true);
    }
}

enter image description here

Note, that you could just as easily draw numbers above the ticks. The drawString-calls would go through the same transformation and get nicely "tilted" along the line.

aioobe
  • 413,195
  • 112
  • 811
  • 826
  • Interesting, I believe 'AffineTransform' is the keyword I was looking for. Google produced this tutorial with very similar components: http://www.glyphic.com/transform/applet/1intro.html Now I just have to understand how to apply it to my problem. – Matt Aug 15 '10 at 17:43
  • You apply it by first translating (moving) to desired start-point, then by rotating by the desired angle (atan2(dy, dx)). Combine the two transformations with AffineTransform.concatenate. – aioobe Aug 15 '10 at 19:26
  • 1
    That looks great. I need to make myself more familiar with the Transform stuff. – jjnguy Aug 15 '10 at 19:30
1

Things that need noting:

  • A perpendicular line has a slope of -1/oldslope.
  • In order to support lines in any direction, you need to do it parametrically
  • Thus, you have dy and dx across the original line, which means that newdx=dy; newdy=-1*dx.
  • If you have it such that <dx, dy> is a unit vector (sqrt(dx*dx+dy+dy)==1, or dx==cos(theta); dy=sin(theta) for some theta), you then just need to know how far apart you want the tick marks.
  • sx, sy are your start x and y
  • length is the length of the line
  • seglength is the length of the dashes
  • dx, dy is the slopes of the original line
  • newdx, newdy are the (calculated above) slopes of the cross lines

Thus,

  1. Draw a line from <sx,sy> (start x,y) to <sx+dx*length,sy+dy*length>
  2. Draw a set of lines (for(i=0;i<=length;i+=interval) from <sx+dx*i-newdx*seglength/2,sy+dy*i-newdy*seglength/2> to <sx+dx*i+newdx*seglength/2,sy+dy*i+newdy*seglength/2>
zebediah49
  • 7,467
  • 1
  • 33
  • 50
  • I like this answer very much because it attempts to explain the geometry. Unfortunately, I'm having trouble wrapping my head around it. I plugged in the formulas you described and I'm getting very odd results for newdy & newdx. (negative values that fall far outside the range of coordinates on my panel) Again, my geometry is elementary at best, but I'm wondering if the formula is for a cartesian coordinate system. Java's coordinates place 0,0 in the top left corner of a panel. – Matt Aug 15 '10 at 18:30
1

I hope you know matrix multiplication. In order to rotate a line you need to multiple it by rotation matrix. (I coudln't draw a proper matrix but assume both line are not separated)

|x'| = |cos(an) -sin(an)| |x|

|y`| = |sin(an)  cos(an)| |y|

The old points are x,y and the new is x',y'. Let us illustrate by an example, lets say you have a vertical line from (0,0) to (0,1), now you want to rotate it by 90 degrees. (0,0) will remain zero so lets just see what happens to (0,1)

|x'| = |cos(90) -sin(90)| |0|

|y`| = |sin(90)  cos(90)| |1|

==

|1 0| |0|

|0 1| |1|

==

| 1*0 + 0*1|

| 0*0 + 1*1|

== |0|

   |1|

you get to horizontal line (0,0),(0,1) like you would expect.

Hope it helps,
Roni

jjnguy
  • 136,852
  • 53
  • 295
  • 323
roni bar yanai
  • 1,492
  • 10
  • 12