Reputation: 323
I need to make a cross with two different colors of segments like in the picture below.
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 :
Upvotes: 1
Views: 204
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:
Upvotes: 1
Reputation: 209358
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();
}
}
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();
}
}
I don't think this can be used for arbitrary paths, but it will work for any straight line.
Upvotes: 2
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
Upvotes: 2
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