ValentinDP
ValentinDP

Reputation: 323

How to create a cross with segments of different colors with adaptable size in JavaFX?

I need to make a cross with two different colors of segments like in the picture below.

enter image description here

The segments can change in size, it's an element to take into account via a binding or other.

How could I make this graphic element in JavaFX ?

I looked with a dotted line but I don't want a space between each segment but a different color. Do I have to put two supperposed lines?

I used JavaFX 17.

Thanks for your help

Edit 01/04 :

I have done some tests but the dash does not fit correctly.

I would like to space each stroke 50px apart on the vertical line and 9,7px apart on the horizontal line but they don't give me the right dimension while I fill the table correctly.

Am I entering the data wrong?

Code :

    public void initialize(URL url, ResourceBundle resourceBundle)
    {
        var line = new Line(200, 200, 200, 600);
        line.setStroke(Paint.valueOf("#FF0000"));
        line.setStrokeWidth(10.0);

        var line2 = new Line(200,200,200,600);
        line2.setStroke(Paint.valueOf("#00FF13"));
        line2.setStrokeWidth(10.0);
        line2.getStrokeDashArray().addAll(50.0d, 50.0d);

        var line3 = new Line(250, 650, 650, 650);
        line3.setStroke(Paint.valueOf("#FF0000"));
        line3.setStrokeWidth(10.0);

        var line4 = new Line(250, 650, 650, 650);
        line4.setStroke(Paint.valueOf("#00FF13"));
        line4.setStrokeWidth(10.0);
        line4.getStrokeDashArray().addAll(9.7d, 9.7d);

        var root = new Group(line, line2, line3, line4);

        anchorPane.getChildren().add(root);
    }

Code result :

enter image description here

Upvotes: 1

Views: 204

Answers (4)

DaveB
DaveB

Reputation: 2143

As @James_D has pointed out, LinearGradient is the way to go. Here's my approach, which uses Rectangle, puts in the LinearGradient, then rotates and repositions the Rectangles to make the crosshairs.

class CrossHair : Application() {
   override fun start(primaryStage: Stage?) {
      primaryStage?.run {
         scene = Scene(createContent(), 500.0, 400.0)
         show()
      }
   }

   private fun createContent(): Region {

      return Pane().apply {
         children += rectangle(150.0, 4).apply {
            translateX = 170.0
            translateY = 100.0
            rotate = 90.0
         }
         children += rectangle(150.0, 4).apply {
            translateX = 170.0
            translateY = 290.0
            rotate = 90.0
         }
         children += rectangle(150.0, 4).apply {
            translateX = 70.0
            translateY = 195.0
         }
         children += rectangle(150.0, 4).apply {
            translateX = 270.0
            translateY = 195.0
         }
      }
   }
}

private fun rectangle(length: Double, repeats: Int) = Rectangle(length, 3.0).apply {
   val stops = mutableListOf<Stop>(Stop(0.0, Color.GREEN), Stop(0.49, Color.GREEN), Stop(0.5, Color.ORANGERED))
   fill = LinearGradient(0.0, 0.0, length / repeats, 0.0, false, CycleMethod.REPEAT, stops)
}

fun main() {
   Application.launch(CrossHair::class.java)
}

Kotlin, obviously.

Different from @James_D, the LinearGradient width is set by using the length of the Rectangle and the number of repeats required. It would be trivial to put an extension function on Rectangle to recalculate the LinearGradient if the number of repeats needed to change.

Note that there's only about 3 lines of code that does the gradient stuff, the rest of this is layout.

For scaling I'd be tempted to just put a scale translation on the whole Pane and use it as a unit.

Anyways, it looks like this:

enter image description here

Upvotes: 1

James_D
James_D

Reputation: 209358

Solution using individual lines (no super-imposing of dashed lines for each color)

Note I have replaced your colors with colors that are friendly to those who suffer from color-blindness or color-weakness.

You can do this with individual lines, using a linear gradient for the stroke.

For example, to create a linear gradient which repeats two colors with 50px vertical dashes, create a linear gradient from (0,0) to (0,100) with the first color at 0 and at 50%, and the second color at 50% and 100%:

    Paint verticalStroke = new LinearGradient(0,0,0,100, false,
            CycleMethod.REPEAT,
            new Stop(0, color1),
            new Stop(0.5, color1),
            new Stop(0.5, color2),
            new Stop(1, color2)
    );

or

    Paint verticalStroke = LinearGradient.valueOf(
            "linear-gradient(from 0px 0px to 0px 100px, repeat, #FC8D62 0%, #FC8D62 50%, #66C2A5 50%, #66C2A5 100%)"
    );

Here is a complete working example. The custom pane lays the lines out in a cross, which reacts to the changing size of the pane. The dashing effect of the lines remains independent of the line size.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.*;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {

        final Color color1 = Color.valueOf("#FC8D62");
        final Color color2 = Color.valueOf("#66C2A5");
        
        final double verticalDashSize = 50;
        final double horizontalDashSize = 9.7;

        Line upperVerticalLine = createLine();
        Line lowerVerticalLine = createLine();
        Line leftHorizontalLine = createLine();
        Line rightHorizontalLine = createLine();

        Stop[] stops = new Stop[] {
                new Stop(0, color1),
                new Stop(0.5, color1),
                new Stop(0.5, color2),
                new Stop(1, color2)
        };

        Paint verticalStroke = new LinearGradient(
                0,0,0,2*verticalDashSize, false,
                CycleMethod.REPEAT, stops
        );

        upperVerticalLine.setStroke(verticalStroke);
        lowerVerticalLine.setStroke(verticalStroke);

        Paint horizontalStroke = new LinearGradient(
                0, 0, 2*horizontalDashSize, 0, false,
                CycleMethod.REPEAT, stops
        );

        leftHorizontalLine.setStroke(horizontalStroke);
        rightHorizontalLine.setStroke(horizontalStroke);

        Pane root = new Pane(upperVerticalLine, lowerVerticalLine, leftHorizontalLine, rightHorizontalLine) {
            
            private static final double PAD = 20 ;
            @Override
            protected void layoutChildren() {
                double w = getWidth();
                double h = getHeight();
                upperVerticalLine.setStartX(w/2);
                upperVerticalLine.setEndX(w/2);
                upperVerticalLine.setStartY(PAD);
                upperVerticalLine.setEndY(h/2-PAD);
                lowerVerticalLine.setStartX(w/2);
                lowerVerticalLine.setEndX(w/2);
                lowerVerticalLine.setStartY(h/2+PAD);
                lowerVerticalLine.setEndY(h-PAD);

                leftHorizontalLine.setStartX(PAD);
                leftHorizontalLine.setStartY(h/2);
                leftHorizontalLine.setEndX(w/2-PAD);
                leftHorizontalLine.setEndY(h/2);
                rightHorizontalLine.setStartX(w/2+PAD);
                rightHorizontalLine.setStartY(h/2);
                rightHorizontalLine.setEndX(w-PAD);
                rightHorizontalLine.setEndY(h/2);
            }

        };
        
        Scene scene =new Scene(root, 800, 800);
        stage.setScene(scene);
        stage.show();

    }

    private Line createLine() {
        Line line = new Line();
        line.setStrokeWidth(10);
        return line ;
    }

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

enter image description here


Update to address comment

Note this is not restricted to horizontal or vertical lines. As long as the startX, startY, endX, and endY parameters of the LinearGradient are chosen to be parallel to the line, this will work. If the slope of the line might change, the stroke will need to be updated accordingly. The following version creates diagonal lines which will be at arbitrary slope if the window is resized:

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

import java.io.IOException;
import java.util.List;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {

        final Color color1 = Color.valueOf("#FC8D62");
        final Color color2 = Color.valueOf("#66C2A5");

        final double verticalDashSize = 50;
        final double horizontalDashSize = 9.7;

        Line upperVerticalLine = createLine();
        Line lowerVerticalLine = createLine();
        Line leftHorizontalLine = createLine();
        Line rightHorizontalLine = createLine();

        Line upperLeftDiag = createLine();
        Line upperRightDiag = createLine();
        Line lowerLeftDiag = createLine();
        Line lowerRightDiag = createLine();

        Stop[] stops = new Stop[] {
                new Stop(0, color1),
                new Stop(0.5, color1),
                new Stop(0.5, color2),
                new Stop(1, color2)
        };


        Pane root = new Pane(upperVerticalLine, lowerVerticalLine, leftHorizontalLine, rightHorizontalLine,
                upperLeftDiag, upperRightDiag, lowerLeftDiag, lowerRightDiag) {

            private static final double PAD = 20 ;
            @Override
            protected void layoutChildren() {
                double w = getWidth();
                double h = getHeight();
                upperVerticalLine.setStartX(w/2);
                upperVerticalLine.setEndX(w/2);
                upperVerticalLine.setStartY(PAD);
                upperVerticalLine.setEndY(h/2-PAD);

                lowerVerticalLine.setStartX(w/2);
                lowerVerticalLine.setEndX(w/2);
                lowerVerticalLine.setStartY(h/2+PAD);
                lowerVerticalLine.setEndY(h-PAD);

                leftHorizontalLine.setStartX(PAD);
                leftHorizontalLine.setStartY(h/2);
                leftHorizontalLine.setEndX(w/2-PAD);
                leftHorizontalLine.setEndY(h/2);

                rightHorizontalLine.setStartX(w/2+PAD);
                rightHorizontalLine.setStartY(h/2);
                rightHorizontalLine.setEndX(w-PAD);
                rightHorizontalLine.setEndY(h/2);

                upperLeftDiag.setStartX(PAD);
                upperLeftDiag.setStartY(PAD);
                upperLeftDiag.setEndX(w/2-PAD);
                upperLeftDiag.setEndY(h/2-PAD);

                upperRightDiag.setStartX(w-PAD);
                upperRightDiag.setStartY(PAD);
                upperRightDiag.setEndX(w/2+PAD);
                upperRightDiag.setEndY(h/2-PAD);

                lowerLeftDiag.setStartX(PAD);
                lowerLeftDiag.setStartY(h-PAD);
                lowerLeftDiag.setEndX(w/2-PAD);
                lowerLeftDiag.setEndY(h/2+PAD);

                lowerRightDiag.setStartX(w-PAD);
                lowerRightDiag.setStartY(h-PAD);
                lowerRightDiag.setEndX(w/2+PAD);
                lowerRightDiag.setEndY(h/2+PAD);

                List.of(upperVerticalLine, lowerVerticalLine, leftHorizontalLine, rightHorizontalLine,
                                upperLeftDiag, upperRightDiag, lowerLeftDiag, lowerRightDiag)
                        .forEach(
                        line -> createGradientForLine(line, 25, color1, color2)
                );
            }

            private void createGradientForLine(Line line, double segmentLength, Color color1, Color color2) {
                Point2D lineVector = new Point2D(line.getEndX(), line.getEndY()).subtract(line.getStartX(), line.getStartY());
                Point2D normalizedVector = lineVector.normalize().multiply(2*segmentLength);
                Stop[] stops = new Stop[] {
                    new Stop(0, color1),
                    new Stop(0.5, color1),
                    new Stop(0.5, color2),
                    new Stop(1, color2)
                };
                LinearGradient gradient = new LinearGradient(0, 0, normalizedVector.getX(), normalizedVector.getY(), false,
                        CycleMethod.REPEAT, stops);
                line.setStroke(gradient);
            }

        };

        Scene scene =new Scene(root, 800, 800);
        stage.setScene(scene);
        stage.show();

    }

    private Line createLine() {
        Line line = new Line();
        line.setStrokeWidth(10);
        return line ;
    }

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

enter image description here

enter image description here

I don't think this can be used for arbitrary paths, but it will work for any straight line.

Upvotes: 2

Giovanni Contreras
Giovanni Contreras

Reputation: 2569

making lines using Group as local coordinate

This approach is based on Mipa's answer.

Every pair of lines are overlaped , but we can make an offset to see the line underneath with setStrokeDashOffset method . Every line is a child of Group node and it's used as local coordinate system . once a new cross is created , we can put it wherever we want without changing any local coordinate . This is a single class javafx you can try . Sorry for hardcode

App.java

public class App extends Application {

    @Override
    public void start(Stage stage) {

        var cross = getCross(400, 20, 5, 30, Color.GREEN, Color.CORAL);
        var cross1 = getCross(100, 10, 3, 10, Color.YELLOW, Color.VIOLET);

        var scene = new Scene(new HBox(cross, cross1), 640, 480);
        stage.setScene(scene);
        stage.setTitle("cross ");
        stage.show();
    }

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

    private Group getCross(double size, double centerOffset, int strokeWidth, double dashSize, Color color1, Color color2) {

// up lines 
        var line = new Line(0, -centerOffset, 0, -size / 2);
        line.setStroke(color1);
        line.setStrokeWidth(strokeWidth);
        line.getStrokeDashArray().add(dashSize);

        var line1 = new Line(0, -centerOffset, 0, -size / 2);
        line1.setStroke(color2);
        line1.setStrokeWidth(strokeWidth);
        line1.getStrokeDashArray().add(dashSize);
        line1.setStrokeDashOffset(dashSize);

// down lines 
        var line2 = new Line(0, centerOffset, 0, size / 2);
        line2.setStroke(color1);
        line2.setStrokeWidth(strokeWidth);
        line2.getStrokeDashArray().add(dashSize);

        var line3 = new Line(0, centerOffset, 0, size / 2);
        line3.setStroke(color2);
        line3.setStrokeWidth(strokeWidth);
        line3.setStrokeDashOffset(dashSize);
        line3.getStrokeDashArray().add(dashSize);

        // right lines        
        var line4 = new Line(centerOffset, 0, size / 2, 0);
        line4.setStroke(color1);
        line4.setStrokeWidth(strokeWidth);
        line4.getStrokeDashArray().add(dashSize);

        var line5 = new Line(centerOffset, 0, size / 2, 0);
        line5.setStroke(color2);
        line5.setStrokeWidth(strokeWidth);
        line5.setStrokeDashOffset(dashSize);
        line5.getStrokeDashArray().add(dashSize);

        // left lines        
        var line6 = new Line(-centerOffset, 0, -size / 2, 0);
        line6.setStroke(color1);
        line6.setStrokeWidth(strokeWidth);
        line6.getStrokeDashArray().add(dashSize);

        var line7 = new Line(-centerOffset, 0, -size / 2, 0);
        line7.setStroke(color2);
        line7.setStrokeWidth(strokeWidth);
        line7.setStrokeDashOffset(dashSize);
        line7.getStrokeDashArray().add(dashSize);

        return new Group(line, line1, line2, line3, line4, line5, line6, line7);
    }

}

Result

dashed lines javafx

Upvotes: 2

mipa
mipa

Reputation: 10650

You could draw two dashed lines on top of each other. One in red and the other in green. In order to use the second one to fill the gaps you would just have to set the proper dash-offset.

Upvotes: 3

Related Questions