2

I'm drawing a graph with two points of each point having a line with a weight.

for example graph: point "15" to point "16" line with the weight of 1.872 and point "16" to point "15" with the weight of 1.567.

take a look at my graph for now:

enter image description here

I want to draw a String with always parallel (adjacent) to the line.

I calculated the slope for the straight and the angel I did calculate is the arctan of this slope:

I had use this function to rotate the string:

public static void drawRotate(Graphics2D g2d, double x, double y, double angle, String text) {
    g2d.translate((float)x,(float)y);
    g2d.rotate(Math.toRadians(angle));
    g2d.drawString(text,0,0);
    g2d.rotate(-Math.toRadians(angle));
    g2d.translate(-(float)x,-(float)y);
}

With the angle of arctan((y2-y1)/(x2-x1)= the slope of the line ) and it didn't work well.

How can I rotate this String to run parallel always to the line I draw?

My goal: Draw the String like this example

enter image description here

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
ATB
  • 119
  • 1
  • 9
  • I'm not certain what you mean by vertical to the line. Imo, horizontal and vertical are absolute positions so I don't understand why you would have to rotate the String. Do you mean perpendicular to the line? Can you draw an example. – WJS Dec 03 '21 at 19:22
  • 2
    @WJS I'm Sorry about that. I uploaded an example. I will appreciate that if u take a look. – ATB Dec 03 '21 at 19:34
  • [Edit] to post a [mre]. – Andrew Thompson Dec 03 '21 at 21:10

1 Answers1

6

Here is a quick demo to be used as a guide on how it might be done. I omitted some things like the arrowheads since that is just busy work. And I guesstimated on the label positions. I would recommend you read about the three argument version of Graphics.rotate() and RenderingHints and anti-aliasing to smooth the lines.

You may want to write general methods to facilitate positioning the text and labels based on font size.

But I believe your primary problem was doing int division when calculating the slope.

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class GraphicsExample extends JPanel {
    JFrame f = new JFrame("Draw Vector");
    final static int WIDTH = 500;
    final static int HEIGHT = 500;
    String A = "1.567 [B->A]";
    String B = "1.862 [A->B]";
    public static void main(String[] args) {
        SwingUtilities.invokeLater(()-> new GraphicsExample().start());
    }
    public void start() {
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
    public Dimension getPreferredSize() {
        return new Dimension(WIDTH, HEIGHT);
    }
    
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        int x1= 50;int y1 = 400;
        int x2 = 400; int y2 = 200;
        // copy the graphics context.
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        int diameter = 20;
        drawLine(g2d,x1+diameter/2, y1+diameter/2, x2+diameter/2, y2+diameter/2);
        g2d.setFont(new Font("Arial", Font.PLAIN, 18));
        drawEndPoint(g2d,x1,y1, diameter, "A");
        drawEndPoint(g2d,x2,y2, diameter, "B");
        double angle = Math.atan((double)(y1-y2)/(x1-x2));
        g2d.rotate(angle,x1,y1);
        
        // based on font, this computes the placement of the Strings
        FontMetrics fm = g2d.getFontMetrics();
        int width = SwingUtilities.computeStringWidth(fm, A); // use for both
        g2d.setColor(Color.black);
        g2d.drawString(A, x1 + ((x2-x1) - width)/2, y1);
        g2d.drawString(B, x1 + ((x2-x1) - width)/2, y1+ 30);
        // discard the context.
        g2d.dispose();
    }
    
    public void drawEndPoint(Graphics2D g2d, int x, int y, int diameter, String label) {
        
        g2d.setColor(Color.BLUE);
        g2d.drawString(label, x, y);
        g2d.fillOval(x,y,diameter, diameter);
        g2d.setColor(Color.RED);
        g2d.setStroke(new BasicStroke(2f));
        g2d.drawOval(x,y,diameter, diameter);
    }
    public void drawLine(Graphics2D g2d, int x1, int y1, int x2, int y2) {
        g2d.setColor(Color.RED);
        g2d.setStroke(new BasicStroke(3f));
        g2d.drawLine(x1,y1,x2,y2);
    }
}

Shows

enter image description here

WJS
  • 36,363
  • 4
  • 24
  • 39
  • For what is worth, I endorse this solution. Everything on the code is done following best practices I know of. From building a Swing application to the use of Java graphics and painting components. – hfontanez Dec 03 '21 at 21:29
  • 4
    This is a good solution, the only recommendation I might make is to make a "copy" of the graphics context before applying transformations and the dispose of the copy when you're done. Since `Graphics` is a shared resource, applying a transformation to it (and forgetting to remove it) will pass that transformation onto the next component which needs to be painted. And because I'm lazy, I'd be tempted to `translate` the `Graphics` offset to the middle point of the rotation, then you could just use `0x0` as the anchor point ;) – MadProgrammer Dec 03 '21 at 21:30
  • @MadProgrammer I thought about that and intentionally set the font ahead of calling the methods. But outside of that controlled environment I have never actually had the problem which you describe. In past tests I have checked the Identify hashCode and it "appears" that I get a new graphic context each time (if that is a believable indication). In the above case, the colors seem to revert back to their default values. But I would be interested in seeing where this could potentially cause problems. – WJS Dec 03 '21 at 21:41
  • 1
    @wjs yes, things like "color" (and possibly font) are default to the components settings as part of the painting process, but things like transformations aren't - I had an issue with a developer who set the anti-aliasing on a single component, which would then cause flickering issues through out the whole app, based on the order the components where painted, it was a complete pain in the ... code to track down and identify - just because it's not happened to you doesn't mean it can't happen – MadProgrammer Dec 03 '21 at 21:52
  • *just because it's not happened to you doesn't mean it can't happen* Reminds me of thread programming sans synchronization. BTW, I added your recommendation. – WJS Dec 03 '21 at 21:53
  • 1
    @WJS I should also point I did a quick test on my Mac using Java 16 and couldn't produce an issue, but this could either be changes to Java 16 or the implementation on MacOS - so, yea cross platform development – MadProgrammer Dec 03 '21 at 22:49