Affine transforms for graph, not for text and labels

痞子三分冷 提交于 2019-12-05 01:10:24

问题


This post is a suite to an answer I made to question: Transforming a shape

Here is the image I want:

Here is the image a simple program produces, as you can see the text is rotated. I want horizontal text:

The canvas is scaled, translated, rotated to do the drawing, so the text is not displayed horizontaly and the font size need to be extremely reduced (1.4). The program is wrote in Java (awt and JavaFX) but the problem is not language or technology relevant, so any suggestion is welcome.

Here is the simple program:

import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;

public class TransRotScale extends Application {

   private static void drawGraph( GraphicsContext g ) {
      //---
      g.scale( 10.0, 10.0 );
      g.rotate( Math.toDegrees( Math.atan2( -15.0, 40.0 )));
      g.translate( -8, -10 );
      //---
      g.setStroke( Color.DARKRED );
      g.setLineWidth( LINE_WIDTH );
      g.strokeLine( 10, 20, 10, 30 );
      g.strokeLine( 10, 30, 50, 30 );
      g.strokeLine( 50, 30, 50, 35 );
      //---
      g.setFill( Color.BLACK );
      g.fillOval( 50-ENDPOINT_RADIUS, 35-ENDPOINT_RADIUS,
         ENDPOINT_DIAMETER, ENDPOINT_DIAMETER );
      g.fillOval( 10-ENDPOINT_RADIUS, 20-ENDPOINT_RADIUS,
         ENDPOINT_DIAMETER, ENDPOINT_DIAMETER );
      //---
      g.setFill( Color.LIGHTSALMON );
      g.fillOval( 10-ENDPOINT_RADIUS, 30-ENDPOINT_RADIUS,
         ENDPOINT_DIAMETER, ENDPOINT_DIAMETER );
      g.fillOval( 50-ENDPOINT_RADIUS, 30-ENDPOINT_RADIUS,
         ENDPOINT_DIAMETER, ENDPOINT_DIAMETER );
      //---
      g.setStroke( Color.DARKGRAY );
      g.setFont( Font.font( Font.getDefault().getFamily(), 1.4 ));
      g.setLineWidth( 0.1 );
      g.setTextAlign( TextAlignment.CENTER );
      g.setTextBaseline( VPos.BOTTOM );
      g.strokeText( "[10, 20]", 10, 20-ENDPOINT_RADIUS );
      g.setTextBaseline( VPos.TOP );
      g.strokeText( "[10, 30]", 10, 30+ENDPOINT_RADIUS );
      g.setTextBaseline( VPos.BOTTOM );
      g.strokeText( "[50, 30]", 50, 30-ENDPOINT_RADIUS );
      g.setTextBaseline( VPos.TOP );
      g.strokeText( "[50, 35]", 50, 35+ENDPOINT_RADIUS );
   }

   @Override
   public void start( Stage primaryStage ) throws Exception {
      BorderPane bp = new BorderPane();
      Canvas canvas = new Canvas( 540, 240 );
      bp.setCenter( canvas );
      drawGraph( canvas.getGraphicsContext2D());
      primaryStage.setScene( new Scene( bp ));
      primaryStage.centerOnScreen();
      primaryStage.show();
   }

   public static final double ENDPOINT_RADIUS   = 2.0;
   public static final double ENDPOINT_DIAMETER = 2.0*ENDPOINT_RADIUS;
   public static final double LINE_WIDTH        = 1.0;

   public static void main( String[] args ) {
      launch();
   }
}

In the program used to display first image (the goal), I use two canvases, the first canvas is scaled, translated, rotated to do the drawing without any text and the second canvas is used only to draw labels horizontally, using java.awt.geom.AffineTransform to compute coordinates to match the item displayed in the first canvas. Both canvases are displayed superposed, they are transparent.


回答1:


This is what suggest Alexander Kirov, if I understand well:

import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polyline;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;

public class TransRotScal extends Application {

   @Override
   public void start( Stage primaryStage ) throws Exception {
      Pane pane = new Pane();
      pane.setScaleX( 10.0 );
      pane.setScaleY( 10.0 );
      pane.setRotate( theta );
      pane.setTranslateX( 468.0 );
      pane.setTranslateY( 152.0 );
      Polyline line = new Polyline( 10,20, 10,30, 50,30, 50,35 );
      line.setStroke( Color.DARKRED );
      Circle   c0   = new Circle( 10, 20, 2, Color.BLACK );
      Circle   c1   = new Circle( 10, 30, 2, Color.LIGHTSALMON );
      Circle   c2   = new Circle( 50, 30, 2, Color.LIGHTSALMON );
      Circle   c3   = new Circle( 50, 35, 2, Color.BLACK );
      Text     t0   = createText( 10, 20, "[10,20]", VPos.BOTTOM );
      Text     t1   = createText( 10, 30, "[10,30]", VPos.TOP );
      Text     t2   = createText( 50, 30, "[50,30]", VPos.BOTTOM );
      Text     t3   = createText( 50, 35, "[50,35]", VPos.TOP );
      pane.getChildren().addAll( line, c0, c1, c2, c3, t0, t1, t2, t3 );
      primaryStage.setScene( new Scene( pane ));
      primaryStage.centerOnScreen();
      primaryStage.setWidth ( 580 );
      primaryStage.setHeight( 280 );
      primaryStage.show();
   }

   private Text createText( int x, int y, String label, VPos vPos ) {
      Text text = new Text( x, y, label );
      text.setFill( Color.DARKGRAY );
      text.setFont( Font.font( Font.getDefault().getFamily(), 1.4 ));
      text.rotateProperty().set( -theta );
      text.textAlignmentProperty().setValue( TextAlignment.CENTER );
      text.setX( text.getX() - text.getBoundsInLocal().getWidth()/2.0);
      text.textOriginProperty().set( vPos );
      if( vPos == VPos.BOTTOM ) {
         text.setY( text.getY() - 2 );
      }
      else {
         text.setY( text.getY() + 2 );
      }
      return text;
   }

   private final double theta = Math.toDegrees( Math.atan2( -15.0, 40.0 ));

   public static void main( String[] args ) {
      launch();
   }
}

It works but it use Node in place of canvas and the values to adjust texts are obtained by iterative tries (a lot!); I don't know how to calculate them.

Alexander, you may edit this post or post your own to complete it, in the later case I'll delete this.

Here is the result, note the approximative placement of text around discs:




回答2:


Instead of drawing the string directly onto the Graphics object can you create a GlyphVector from the string instead and draw that to the Graphics object? The advantage to this approach would be that the GlyphVector can have its own transform which you could use to effectively cancel the rotation of the canvas. I don't remember the exact details to creating the glyphs, but you need a Font and a FontRenderContext...both of which are already available to the Graphics context.



来源:https://stackoverflow.com/questions/15742563/affine-transforms-for-graph-not-for-text-and-labels

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!