diff --git a/pom.xml b/pom.xml index 40ec1d5..9c26dc2 100644 --- a/pom.xml +++ b/pom.xml @@ -15,17 +15,16 @@ UTF-8 2.4.4 - 3.2.3 - 3.3.0 - 3.12.1 - 0.8.11 - 3.5.0 - 5.10.1 - 2.16.1 - 1.4.14 - 4.1.104.Final + 3.2.5 + 3.4.1 + 3.13.0 + 0.8.12 + 5.10.2 + 2.17.1 + 1.5.6 + 4.1.110.Final 2.73.6 - 21.0.1 + 22.0.1 9.0.10 @@ -133,11 +132,6 @@ jacoco-maven-plugin ${jacoco-maven-plugin.version} - - org.codehaus.mojo - build-helper-maven-plugin - ${build-helper-maven-plugin.version} - diff --git a/urban-spork-client-gui/resource/console.css b/urban-spork-client-gui/resource/console.css index cf3317d..77ea7b2 100644 --- a/urban-spork-client-gui/resource/console.css +++ b/urban-spork-client-gui/resource/console.css @@ -151,8 +151,8 @@ GridPane { -fx-background-color: #000; } -.chart-series-line { - -fx-stroke-width: 2; +.chart { + -fx-max-height: 250; } .chart-legend { @@ -167,6 +167,10 @@ GridPane { -fx-background-color: transparent; } +.chart-series-line { + -fx-stroke-width: 2; +} + .default-color0.chart-series-line { -fx-stroke: #0a84ff; } diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/console/Console.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/console/Console.java index 56b7b41..139718b 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/console/Console.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/console/Console.java @@ -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; @@ -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 // ==================== @@ -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); diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/spine/CatmullRom.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/spine/CatmullRom.java new file mode 100644 index 0000000..fbce621 --- /dev/null +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/spine/CatmullRom.java @@ -0,0 +1,98 @@ +package com.urbanspork.client.gui.spine; + +import javafx.geometry.Point2D; + +/** + * Java implement of Centripetal Catmull–Rom spline + * + * @param alpha The alpha value ranges from 0 to 1 + * + * @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; + } +} diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChart.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChart.java index c21c25a..f8fd602 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChart.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChart.java @@ -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 { @@ -27,7 +32,52 @@ public TrafficCounterLineChart(ObjectProperty instance) { public LineChart init() { NumberAxis xAxis = new NumberAxis(0, WINDOW, WINDOW); NumberAxis yAxis = new NumberAxis(); - LineChart lineChart = new LineChart<>(xAxis, yAxis); + yAxis.setTickLabelsVisible(false); + yAxis.setTickMarkVisible(false); + yAxis.lookup(".axis-minor-tick-mark").setVisible(false); + LineChart lineChart = new LineChart<>(xAxis, yAxis) { + @Override + protected void layoutPlotChildren() { + super.layoutPlotChildren(); + ObservableList> data = getData(); + data.stream().map(Series::getNode).mapMulti((node, consumer) -> { + if (node instanceof Path path) { + consumer.accept(path); + } + }).forEach(this::curve); + } + + private void curve(Path path) { + ObservableList elements = path.getElements(); + if (elements.size() > 3) { + curve(elements); + } + } + + private void curve(ObservableList 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> data = lineChart.getData(); diff --git a/urban-spork-common/src/com/urbanspork/common/util/LruCache.java b/urban-spork-common/src/com/urbanspork/common/util/LruCache.java index a6ab60c..f46c63d 100644 --- a/urban-spork-common/src/com/urbanspork/common/util/LruCache.java +++ b/urban-spork-common/src/com/urbanspork/common/util/LruCache.java @@ -4,7 +4,6 @@ import io.netty.util.Timeout; import java.time.Duration; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -16,7 +15,7 @@ public class LruCache { final Duration timeToLive; final HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS); final BiConsumer afterExpired; - final Map> inner = Collections.synchronizedMap(new LinkedHashMap<>() { + final Map> inner = new LinkedHashMap<>() { @Override protected boolean removeEldestEntry(Map.Entry> eldest) { boolean flag = size() > capacity; @@ -28,7 +27,7 @@ protected boolean removeEldestEntry(Map.Entry> eldest) { } return flag; } - }); + }; public LruCache(long capacity, Duration timeToLive, BiConsumer afterExpired) { this.capacity = capacity; @@ -73,7 +72,7 @@ public V remove(K key) { } public void release() { - for (Map.Entry> entry : inner.entrySet()) { + for (Map.Entry> entry : Map.copyOf(inner).entrySet()) { afterExpired.accept(entry.getKey(), entry.getValue().value); } timer.stop();