Skip to content

Commit

Permalink
feat(gui): use curves instead of straight lines
Browse files Browse the repository at this point in the history
  • Loading branch information
Zmax0 committed Jun 6, 2024
1 parent 1170da0 commit 91cc296
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 5 deletions.
8 changes: 6 additions & 2 deletions urban-spork-client-gui/resource/console.css
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ GridPane {
-fx-background-color: #000;
}

.chart-series-line {
-fx-stroke-width: 2;
.chart {
-fx-max-height: 250;
}

.chart-legend {
Expand All @@ -167,6 +167,10 @@ GridPane {
-fx-background-color: transparent;
}

.chart-series-line {
-fx-stroke-width: 2;
}

.default-color0.chart-series-line {
-fx-stroke: #0a84ff;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -332,8 +333,8 @@ private JFXTabPane initTabPane() {
tab0.setClosable(false);
// tab1
Tab tab1 = newSingleNodeTab(logTextArea, I18N.getString(I18N.CONSOLE_TAB1_TEXT));
//
Tab tab2 = newSingleNodeTab(new TrafficCounterLineChart(instance).init(), I18N.getString(I18N.CONSOLE_TAB2_TEXT));
// tab2
Tab tab2 = initTrafficTab();
// ====================
// main tab pane
// ====================
Expand Down Expand Up @@ -375,6 +376,16 @@ private Tab newSingleNodeTab(Node node, String tabTitle) {
return tab;
}

private Tab initTrafficTab() {
StackPane stackPane = new StackPane();
stackPane.setAlignment(Pos.TOP_CENTER);
stackPane.getChildren().add(new TrafficCounterLineChart(instance).init());
Tab tab = new Tab(I18N.getString(I18N.CONSOLE_TAB2_TEXT));
tab.setContent(stackPane);
tab.setClosable(false);
return tab;
}

private void addGridPane0Children(GridPane gridPane0) {
// ---------- Grid Children ----------
gridPane0.add(wrap(moveUpServerConfigButton), 1, 13);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.urbanspork.client.gui.spine;

import javafx.geometry.Point2D;

/**
* Java implement of <a href="https://en.wikipedia.org/wiki/Centripetal_Catmull–Rom_spline">Centripetal Catmull–Rom spline</a>
*
* @param alpha The alpha value ranges from 0 to 1
* <ul>
* <li>0.5 for the centripetal spline
* <li>0.0 for the uniform spline
* <li>1.0 for the chordal spline
* </ul>
* @author Zmax0
*/
public record CatmullRom(double alpha) {
/**
* Calculate Catmull-Rom for a sequence of initial points and return the combined curve.
*
* @param points Base points from which the quadruples for the algorithm are taken
* @param segment The number of points include in each curve segment
* @return The combined curve
*/
public Point2D[] interpolate(Point2D[] points, int segment) {
if (points.length < 2) {
throw new IllegalArgumentException("The points parameter must be greater than 2");
}
if (points.length < 3) {
return points;
}
Point2D[] controls = getControls(points);
Point2D[] curve = new Point2D[points.length + (points.length - 1) * segment];
for (int i = 0; i < controls.length - 3; i++) {
curve[i * (segment + 1)] = controls[i + 1];
Point2D[] temp = interpolate(controls[i], controls[i + 1], controls[i + 2], controls[i + 3], segment);
System.arraycopy(temp, 0, curve, i * (segment + 1) + 1, segment);
}
curve[curve.length - 1] = (controls[controls.length - 2]); // last
return curve;
}

private Point2D[] getControls(Point2D[] point) {
Point2D[] controls = new Point2D[point.length + 2];
System.arraycopy(point, 0, controls, 1, point.length);
// set two control points at start C1 and end C2
if (point[0].equals(point[point.length - 1])) { // if is close
// C1 = P(n-1)
controls[0] = point[point.length - 2];
// C2 = P1
controls[controls.length - 1] = point[1];
} else {
// C1 = P0 - (P1 - P0)
controls[0] = point[0].subtract(point[1].subtract(point[0]));
// C2 = Pn + (Pn - P(n-1))
controls[controls.length - 1] = point[point.length - 1].add(point[point.length - 1].subtract(point[point.length - 2]));
}
return controls;
}

/**
* Calculate Catmull-Rom spline curve points starts with p1 and ends with p2
*
* @param p0 The (x,y) point pairs that define the Catmull-Rom spline
* @param p1 The (x,y) point pairs that define the Catmull-Rom spline
* @param p2 The (x,y) point pairs that define the Catmull-Rom spline
* @param p3 The (x,y) point pairs that define the Catmull-Rom spline
* @param segment The number of points to include in each curve segment
* @return points for the resulting curve
*/
public Point2D[] interpolate(Point2D p0, Point2D p1, Point2D p2, Point2D p3, int segment) {
// calculate knots
double t0 = 0;
double t1 = getT(t0, p0, p1);
double t2 = getT(t1, p1, p2);
double t3 = getT(t2, p2, p3);
double step = (t2 - t1) / (segment - 1);
// evaluate the point
Point2D[] curve = new Point2D[segment];
curve[0] = p1;
for (int i = 1; i < segment - 1; i++) {
double t = t1 + i * step;
Point2D a1 = p0.multiply((t1 - t) / (t1 - t0)).add(p1.multiply((t - t0) / (t1 - t0)));
Point2D a2 = p1.multiply((t2 - t) / (t2 - t1)).add(p2.multiply((t - t1) / (t2 - t1)));
Point2D a3 = p2.multiply((t3 - t) / (t3 - t2)).add(p3.multiply((t - t2) / (t3 - t2)));
Point2D b1 = a1.multiply((t2 - t) / (t2 - t0)).add(a2.multiply((t - t0) / (t2 - t0)));
Point2D b2 = a2.multiply((t3 - t) / (t3 - t1)).add(a3.multiply((t - t1) / (t3 - t1)));
curve[i] = b1.multiply((t2 - t) / (t2 - t1)).add(b2.multiply((t - t1) / (t2 - t1)));
}
curve[curve.length - 1] = p2;
return curve;
}

// calculate knots
private double getT(double t, Point2D p0, Point2D p1) {
Point2D d = p1.subtract(p0);
return Math.pow(d.dotProduct(d), alpha * .5) + t;
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.urbanspork.client.gui.traffic;

import com.urbanspork.client.Client;
import com.urbanspork.client.gui.spine.CatmullRom;
import io.netty.handler.traffic.TrafficCounter;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.util.Duration;

public class TrafficCounterLineChart {
Expand All @@ -27,7 +32,49 @@ public TrafficCounterLineChart(ObjectProperty<Client.Instance> instance) {
public LineChart<Number, Number> init() {
NumberAxis xAxis = new NumberAxis(0, WINDOW, WINDOW);
NumberAxis yAxis = new NumberAxis();
LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis) {
@Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
ObservableList<Series<Number, Number>> data = getData();
data.stream().map(Series::getNode).<Path>mapMulti((node, consumer) -> {
if (node instanceof Path path) {
consumer.accept(path);
}
}).forEach(this::curve);
}

private void curve(Path path) {
ObservableList<PathElement> elements = path.getElements();
if (elements.size() > 3) {
curve(elements);
}
}

private void curve(ObservableList<PathElement> elements) {
int size = elements.size();
Point2D[] points = new Point2D[size - 1];
for (int i = 0; i < points.length; i++) {
PathElement e = elements.get(i + 1);
if (e instanceof LineTo lineTo) {
points[i] = new Point2D(lineTo.getX(), lineTo.getY());
}
}
Point2D[] interpolate = new CatmullRom(0.5).interpolate(points, 32);
// update
for (int i = 1; i < size; i++) {
PathElement e = elements.get(i);
if (e instanceof LineTo lineTo) {
lineTo.setX(interpolate[i - 1].getX());
lineTo.setY(interpolate[i - 1].getY());
}
}
// add
for (int i = 0; i < interpolate.length - size; i++) {
elements.add(new LineTo(interpolate[i + size].getX(), interpolate[i + size].getY()));
}
}
};
lineChart.setAnimated(false);
lineChart.setCreateSymbols(false);
ObservableList<XYChart.Series<Number, Number>> data = lineChart.getData();
Expand Down

0 comments on commit 91cc296

Please sign in to comment.