3

I wrote this polar clock today and i am almost finished exept i want to align my text inside the line similar to this. Does anyone know how to do this? Ive tried to use FontRenderContext and font metrics but i cant seem to get it to work. Here is the whole source code so you can compile it and see for yourselves.

import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.Calendar;
import java.util.TimeZone;

public class Clock extends Applet implements Runnable {

int[][] colorsInt = {{20,20,20},{100,100,50},{50,100,100},{10,170,50},{79,29,245},{24,69,234},{253,24,103}};
Color[] colors;
int size;
int radius;
boolean anitalias = false;
static final float HPI = (float)(Math.PI / 180f);

public void start() {
    enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    new Thread(this).start();
}

public void run() {
    setSize(500, 500); // For AppletViewer, remove later.

    // Set up the graphics stuff, double-buffering.
    BufferedImage screen = new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = (Graphics2D)screen.getGraphics();

    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    WritableRaster wr = screen.getRaster();
    Graphics appletGraphics = getGraphics();

    // Some variables to use for the fps.
    long fpstn = 1000000000 / 600;
    int tick = 0, fps = 0, acc = 0;
    long lastTime = System.nanoTime();

    // Vars
    Calendar c;
    size = 500;
    radius = size / 2;
    Arc2D.Float arch;
    float scale, radians;
    long miliSecond;
    int second, minute, hour, month, year, dayOfWeek, dayOfMonth, dayOfYear, daysInMonth, daysInYear;
    float[] tvars = new float[6];
    float[] vars = new float[6];
    String[] names = new String[6];
    FontMetrics fm = g.getFontMetrics();
    Font font = g.getFont();
    FontRenderContext frc = g.getFontRenderContext();
    GlyphVector gv = font.createGlyphVector(frc, "Hello world");
    int length = gv.getNumGlyphs();

    // Init
    initColors();
    for (int i = 0; i < vars.length; i++)
        vars[i] = 0;

    // Game loop.
    while (true) {
        long now = System.nanoTime();
        acc += now - lastTime;
        tick++;
        if (acc >= 1000000000L) {
            acc -= 1000000000L;
            fps = tick;
            tick = 0;
        }

        // Update
        c = Calendar.getInstance();
        miliSecond = c.get(Calendar.MILLISECOND);
        second = c.get(Calendar.SECOND);
        minute = c.get(Calendar.MINUTE);
        hour = c.get(Calendar.HOUR_OF_DAY);
        dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
        dayOfYear = c.get(Calendar.DAY_OF_YEAR);
        dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
        month = c.get(Calendar.MONTH);
        daysInMonth = c.getActualMaximum(Calendar.DAY_OF_MONTH);
        daysInYear = c.getActualMaximum(Calendar.DAY_OF_YEAR);

        tvars[0] = (second * 1000 + miliSecond) / 60000f * 360f;
        tvars[1] = (minute * 60f + second) / 3600f * 360f;
        tvars[2] = (hour * 60f + minute) / 1440f * 360f;
        tvars[3] = ((dayOfWeek - 2) * 24f + hour) / 168f * 360f;
        tvars[4] = ((dayOfMonth - 1) * 24f + hour) / (daysInMonth * 24f) * 360f;
        tvars[5] = dayOfYear / (float)daysInYear * 360f;

        for (int i = 0; i < vars.length; i++) {
            if (tvars[i] - vars[i] > 1) {
                vars[i] += (tvars[i] - vars[i]) / 15;
            } else if(tvars[i] - vars[i] < -1) {
                vars[i] -= (vars[i] - tvars[i]) / 15;
            } else {
                vars[i] = tvars[i];
            }
        }

        names[0] = second + " Second" + (second > 1 ? "s" : "");

        lastTime = now;

        // Render
        g.setColor(colors[0]);
        g.fillRect(0, 0, size, size);
        for (int i = 0; i < vars.length; i++) {

            scale = i / (float)vars.length * radius * 1.7f;
            g.setColor(colors[0]);
            g.fillOval((int)(scale / 2), (int)(scale / 2), (int)(size - scale), (int)(size - scale));
            g.setColor(colors[i + 1]);
            scale += 15;
            arch = new Arc2D.Float(scale / 2, scale / 2, size - scale, size - scale, 450 - vars[i], vars[i], Arc2D.PIE);
            g.fill(arch);

            g.setColor(Color.WHITE);
            radians = (vars[i]) * HPI;// vars[i] - 90
            scale = ((float)(vars.length - i) / (float)vars.length * (float)radius / 2f * 1.7f) + 15f;

            g.translate(radius, radius);
            System.out.println(i + ": " + ((1 - scale / radius) * 2));
            for (int j = 0; j < names[0].length(); j++) {

                char ch = names[0].charAt(j);
                radians = ((vars[i] - (names[0].length() - j) * 2) * (1 + (1 - scale / radius) * 2)) * HPI;
                g.rotate(radians);
                g.drawString(ch + "", 0, -scale);
                g.rotate(-radians);
            }
            g.translate(-radius, -radius);

            /*float x = (float)Math.cos(radians) * scale;
            float y = (float)Math.sin(radians) * (vars.length - i) / vars.length * radius / 2 * 1.7f;
            g.drawRect((int)x + size / 2, (int)y + size / 2, 10, 10);*/

        }
        scale = vars.length / (float)vars.length * radius * 1.7f;
        g.setColor(colors[0]);
        g.fillOval((int)(scale / 2), (int)(scale / 2), (int)(size - scale), (int)(size - scale));

        g.setColor(Color.WHITE);
        g.drawString("FPS " + String.valueOf(fps), 20, 30);

        // Draw the entire results on the screen.
        appletGraphics.drawImage(screen, 0, 0, null);

        do {
            Thread.yield();
        } while (System.nanoTime() - lastTime < 0);
        if (!isActive()) {
            return;
        }
    }
}

public void initColors() {
    colors = new Color[colorsInt.length];
    for (int i = 0; i < colors.length; i++) {
        colors[i] = new Color(colorsInt[i][0], colorsInt[i][1], colorsInt[i][2]);
    }
}

}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
Stas Jaro
  • 4,747
  • 5
  • 31
  • 53

2 Answers2

9

Here's a simple example of rotating text.

Addendum: You'll want to adjust the the text's radial starting point by stringWidth(name[n]). Your program appears to be rotating individual characters in a effort to follow the arc, while the example appears to be drawing the text in a straight line tangent to the arc. The latter approach may prove simpler. For example, this variation centers the labels across the arc's getStartPoint():

for (int i = 0; i < vars.length; i++) {
    ...
    String s = names[0];
    int w = fm.stringWidth(s);
    int h = fm.getHeight() + fm.getMaxDescent();
    Point2D p = arch.getStartPoint();
    int x = (int) p.getX();
    int y = (int) p.getY();
    radians = (vars[i]) * HPI;
    g.rotate(radians, x, y);
    g.drawString(s, x - w / 2, y + h);
    g.rotate(-radians, x, y);
}

enter image description here

For convenience the code above does rotate() to and fro; for comparison, here's the original example showing repeated concatenations of rotate():

enter image description here

import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;

/** @see http://stackoverflow.com/questions/6238037 */
public class RotateText extends JPanel {

    private static final Font f = new Font("Serif", Font.BOLD, 32);
    private static final String s = "Hello World!";
    private static final Color[] colors = {
        Color.red, Color.green, Color.blue, Color.cyan
    };
    private Graphics2D g2d;
    private AffineTransform at;

    public RotateText() {
        setPreferredSize(new Dimension(400, 400));
    }

    @Override
    public void paintComponent(Graphics g) {
        g2d = (Graphics2D) g;
        g2d.setFont(f);
        g2d.setColor(Color.black);
        g2d.fillRect(0, 0, getWidth(), getHeight());
        at = g2d.getTransform();
        int w = this.getWidth();
        int h = this.getHeight();
        int w2 = g2d.getFontMetrics().stringWidth(s) / 2;
        int h2 = 2 * g2d.getFontMetrics().getHeight() / 3;
        render(0, w / 2 - w2, h - h2);
        render(1, h2, h / 2 - w2);
        render(2, w / 2 + w2, h2);
        render(3, w - h2, h / 2 + w2);
        g2d.setTransform(at);
        g2d.setColor(Color.yellow);
        g2d.fillRect(w / 3, h / 3, w / 3, h / 3);
    }

    private void render(int n, int x, int y) {
        g2d.setColor(colors[n]);
        g2d.setTransform(at);
        g2d.rotate(n * Math.PI / 2, x, y);
        g2d.drawString(s, x, y);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            //@Override
            public void run() {
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.add(new RotateText(), BorderLayout.CENTER);
                f.pack();
                f.setVisible(true);
            }
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I already had a version like this working previously with strait strings but thanks for the help. I didn't know you could use the setTransform method the way you did in the example. It really helped. It gave about +30 fps by using a default AffineTransform insead of rotating back; – Stas Jaro Jun 04 '11 at 23:38
  • I don't think rotating the text is enough in this case. You have to be able to draw the text along the arbitrary curve. See my answer below. – Eugene Ryzhikov Jun 04 '11 at 23:39
  • @stas: Excellent! I think keying off `getStartPoint()` may save some computational effort, too. If desired, you may be able to go back and apply one of @eugener's helpful suggestions. – trashgod Jun 05 '11 at 00:01
2

You have to be able to draw text along the curves. There are several ways to do it, but the simplest one is to use Stroke API. You can find an example at http://www.jhlabs.com/java/java2d/strokes/

The other way is using affine transforms. The example is at http://www.java2s.com/Code/Java/2D-Graphics-GUI/Drawtextalongacurve.htm

Eugene Ryzhikov
  • 17,131
  • 3
  • 38
  • 60
  • +1 I see your point; I hadn't seen [Text Along a Path](http://www.jhlabs.com/java/java2d/strokes/TextStroke.java) before. – trashgod Jun 04 '11 at 23:55
  • Thanks but i have already tried both of thoes methods and i cannot seem to get them to work. Maybye im just doing something wrong? – Stas Jaro Jun 04 '11 at 23:56
  • I confess, I didn't like the `RollingText` variation. The glyphs were never _quite_ normal to the baseline. – trashgod Jun 05 '11 at 00:21