|
@@ -0,0 +1,278 @@
|
|
|
+package org.springblade.manager.utils;
|
|
|
+
|
|
|
+import org.jfree.chart.ChartFactory;
|
|
|
+import org.jfree.chart.ChartUtils;
|
|
|
+import org.jfree.chart.JFreeChart;
|
|
|
+import org.jfree.chart.annotations.XYLineAnnotation;
|
|
|
+import org.jfree.chart.axis.NumberAxis;
|
|
|
+import org.jfree.chart.plot.PlotOrientation;
|
|
|
+import org.jfree.chart.plot.XYPlot;
|
|
|
+import org.jfree.chart.renderer.xy.XYSplineRenderer;
|
|
|
+import org.jfree.chart.ui.RectangleInsets;
|
|
|
+import org.jfree.data.xy.XYSeries;
|
|
|
+import org.jfree.data.xy.XYSeriesCollection;
|
|
|
+
|
|
|
+import java.awt.*;
|
|
|
+import java.awt.geom.Ellipse2D;
|
|
|
+import java.io.File;
|
|
|
+import java.io.IOException;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 最大干密度曲线生成器
|
|
|
+ */
|
|
|
+public class SmoothCurveChartToImage {
|
|
|
+ private double[][] dataPoints;
|
|
|
+ private String xAxisLabel;
|
|
|
+ private String yAxisLabel;
|
|
|
+ private String chartTitle;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造函数
|
|
|
+ * @param dataPoints 二维数组,每行表示一个点 [x, y]
|
|
|
+ * @param xAxisLabel X轴标签
|
|
|
+ * @param yAxisLabel Y轴标签
|
|
|
+ * @param chartTitle 图表标题
|
|
|
+ */
|
|
|
+ public SmoothCurveChartToImage(double[][] dataPoints, String xAxisLabel, String yAxisLabel, String chartTitle) {
|
|
|
+ this.dataPoints = dataPoints;
|
|
|
+ this.xAxisLabel = xAxisLabel;
|
|
|
+ this.yAxisLabel = yAxisLabel;
|
|
|
+ this.chartTitle = chartTitle;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成图表并保存为图片
|
|
|
+ * @param filePath 文件路径
|
|
|
+ * @param width 图片宽度
|
|
|
+ * @param height 图片高度
|
|
|
+ */
|
|
|
+ public void generateChart(String filePath, int width, int height) {
|
|
|
+ // 创建数据集
|
|
|
+ XYSeries series = new XYSeries("数据点");
|
|
|
+
|
|
|
+ // 添加数据点
|
|
|
+ for (double[] point : dataPoints) {
|
|
|
+ if (point.length >= 2) {
|
|
|
+ series.add(point[0], point[1]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ XYSeriesCollection dataset = new XYSeriesCollection(series);
|
|
|
+
|
|
|
+ // 创建图表,不显示图例
|
|
|
+ JFreeChart chart = ChartFactory.createXYLineChart(
|
|
|
+ chartTitle,
|
|
|
+ xAxisLabel,
|
|
|
+ yAxisLabel,
|
|
|
+ dataset,
|
|
|
+ PlotOrientation.VERTICAL,
|
|
|
+ false,
|
|
|
+ true,
|
|
|
+ false
|
|
|
+ );
|
|
|
+
|
|
|
+ // 设置中文字体,防止乱码
|
|
|
+ chart.getTitle().setFont(new Font("微软雅黑", Font.BOLD, 16));
|
|
|
+
|
|
|
+ // 获取图表区域
|
|
|
+ XYPlot plot = chart.getXYPlot();
|
|
|
+
|
|
|
+ // 设置X轴和Y轴标签字体
|
|
|
+ NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
|
|
|
+ domainAxis.setLabelFont(new Font("微软雅黑", Font.PLAIN, 12));
|
|
|
+ domainAxis.setTickLabelFont(new Font("微软雅黑", Font.PLAIN, 10));
|
|
|
+
|
|
|
+ NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
|
|
|
+ rangeAxis.setLabelFont(new Font("微软雅黑", Font.PLAIN, 12));
|
|
|
+ rangeAxis.setTickLabelFont(new Font("微软雅黑", Font.PLAIN, 10));
|
|
|
+
|
|
|
+ // 调整Y轴范围,不从0开始
|
|
|
+ double minY = series.getMinY();
|
|
|
+ double maxY = series.getMaxY();
|
|
|
+ double range = maxY - minY;
|
|
|
+
|
|
|
+ // 设置Y轴范围,从最小值下方10%开始,到最大值上方10%结束
|
|
|
+ double lowerBound = minY - range * 0.1;
|
|
|
+ double upperBound = maxY + range * 0.1;
|
|
|
+
|
|
|
+ rangeAxis.setRange(lowerBound, upperBound);
|
|
|
+
|
|
|
+ // 调整X轴范围,使其更加合适
|
|
|
+ double minX = series.getMinX();
|
|
|
+ double maxX = series.getMaxX();
|
|
|
+ double xRange = maxX - minX;
|
|
|
+ domainAxis.setRange(minX - xRange * 0.1, maxX + xRange * 0.1);
|
|
|
+
|
|
|
+ // 使用样条渲染器创建平滑曲线
|
|
|
+ XYSplineRenderer renderer = new XYSplineRenderer();
|
|
|
+ renderer.setSeriesPaint(0, Color.BLUE);
|
|
|
+ renderer.setSeriesStroke(0, new BasicStroke(2.0f));
|
|
|
+ // 显示数据点
|
|
|
+ renderer.setSeriesShapesVisible(0, true);
|
|
|
+ // 设置数据点的形状为圆形
|
|
|
+ renderer.setSeriesShape(0, new Ellipse2D.Double(-3, -3, 6, 6));
|
|
|
+
|
|
|
+ plot.setRenderer(renderer);
|
|
|
+
|
|
|
+ // 设置图表背景和网格线
|
|
|
+ plot.setBackgroundPaint(Color.LIGHT_GRAY);
|
|
|
+ plot.setDomainGridlinePaint(Color.WHITE);
|
|
|
+ plot.setRangeGridlinePaint(Color.WHITE);
|
|
|
+ plot.setDomainGridlinesVisible(true);
|
|
|
+ plot.setRangeGridlinesVisible(true);
|
|
|
+
|
|
|
+ // 计算曲线上的峰值点
|
|
|
+ double[] peakPoint = findPeakOnCurve(dataset);
|
|
|
+ double peakX = peakPoint[0];
|
|
|
+ double peakY = peakPoint[1];
|
|
|
+
|
|
|
+ System.out.println("找到峰值点: (" + peakX + ", " + peakY + ")");
|
|
|
+
|
|
|
+ // 创建一个新的系列只包含峰值点
|
|
|
+ XYSeries peakSeries = new XYSeries("峰值点");
|
|
|
+ peakSeries.add(peakX, peakY);
|
|
|
+ dataset.addSeries(peakSeries);
|
|
|
+
|
|
|
+ // 为峰值点系列设置不同的渲染属性
|
|
|
+ renderer.setSeriesPaint(1, Color.RED);
|
|
|
+ renderer.setSeriesShapesVisible(1, true);
|
|
|
+ renderer.setSeriesShape(1, new Ellipse2D.Double(-3, -3, 6, 6));
|
|
|
+ renderer.setSeriesLinesVisible(1, false); // 不连接线
|
|
|
+
|
|
|
+ // 添加垂直线到X轴
|
|
|
+ XYLineAnnotation xLine = new XYLineAnnotation(
|
|
|
+ peakX, peakY, peakX, plot.getRangeAxis().getLowerBound(),
|
|
|
+ new BasicStroke(1.5f), Color.RED);
|
|
|
+ plot.addAnnotation(xLine);
|
|
|
+
|
|
|
+ // 添加垂直线到Y轴
|
|
|
+ XYLineAnnotation yLine = new XYLineAnnotation(
|
|
|
+ plot.getDomainAxis().getLowerBound(), peakY, peakX, peakY,
|
|
|
+ new BasicStroke(1.5f), Color.RED);
|
|
|
+ plot.addAnnotation(yLine);
|
|
|
+
|
|
|
+ // 设置背景网格线为实线
|
|
|
+ plot.setDomainGridlineStroke(new BasicStroke(1.0f));
|
|
|
+ plot.setRangeGridlineStroke(new BasicStroke(1.0f));
|
|
|
+ plot.setDomainGridlinePaint(Color.BLACK);
|
|
|
+ plot.setRangeGridlinePaint(Color.BLACK);
|
|
|
+ // 确保网格线可见
|
|
|
+ plot.setDomainGridlinesVisible(true);
|
|
|
+ plot.setRangeGridlinesVisible(true);
|
|
|
+ plot.setAxisOffset(new RectangleInsets(0, 0, 0, 0));
|
|
|
+
|
|
|
+ // 保存图表为图片文件
|
|
|
+ try {
|
|
|
+ File file = new File(filePath);
|
|
|
+ ChartUtils.saveChartAsPNG(file, chart, width, height);
|
|
|
+ System.out.println("图表已保存为: " + file.getAbsolutePath());
|
|
|
+ } catch (IOException e) {
|
|
|
+ System.err.println("保存图表时出错: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在曲线上查找峰值点的方法
|
|
|
+ private static double[] findPeakOnCurve(XYSeriesCollection dataset) {
|
|
|
+ XYSeries series = dataset.getSeries(0);
|
|
|
+ int pointCount = series.getItemCount();
|
|
|
+
|
|
|
+ // 获取X和Y范围
|
|
|
+ double minX = series.getMinX();
|
|
|
+ double maxX = series.getMaxX();
|
|
|
+
|
|
|
+ // 在曲线上采样多个点来找到峰值
|
|
|
+ int samples = 1000; // 采样点数量
|
|
|
+ double step = (maxX - minX) / samples;
|
|
|
+
|
|
|
+ double peakX = minX;
|
|
|
+ double peakY = Double.NEGATIVE_INFINITY;
|
|
|
+
|
|
|
+ // 遍历采样点
|
|
|
+ for (int i = 0; i <= samples; i++) {
|
|
|
+ double x = minX + i * step;
|
|
|
+ double y = calculateSplineValue(series, x);
|
|
|
+
|
|
|
+ if (y > peakY) {
|
|
|
+ peakY = y;
|
|
|
+ peakX = x;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return new double[]{peakX, peakY};
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算样条曲线上某点的Y值
|
|
|
+ private static double calculateSplineValue(XYSeries series, double x) {
|
|
|
+ int n = series.getItemCount();
|
|
|
+
|
|
|
+ // 如果x超出数据范围,返回边界值
|
|
|
+ if (n == 0) return 0;
|
|
|
+ if (n == 1) return series.getY(0).doubleValue();
|
|
|
+
|
|
|
+ if (x <= series.getX(0).doubleValue()) {
|
|
|
+ return series.getY(0).doubleValue();
|
|
|
+ }
|
|
|
+ if (x >= series.getX(n - 1).doubleValue()) {
|
|
|
+ return series.getY(n - 1).doubleValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 找到x所在的区间
|
|
|
+ int i = 0;
|
|
|
+ while (i < n - 1 && series.getX(i + 1).doubleValue() < x) {
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取相邻四个点用于三次样条计算
|
|
|
+ double x0 = (i > 0) ? series.getX(i - 1).doubleValue() : series.getX(i).doubleValue();
|
|
|
+ double x1 = series.getX(i).doubleValue();
|
|
|
+ double x2 = series.getX(i + 1).doubleValue();
|
|
|
+ double x3 = (i < n - 2) ? series.getX(i + 2).doubleValue() : series.getX(i + 1).doubleValue();
|
|
|
+
|
|
|
+ double y0 = (i > 0) ? series.getY(i - 1).doubleValue() : series.getY(i).doubleValue();
|
|
|
+ double y1 = series.getY(i).doubleValue();
|
|
|
+ double y2 = series.getY(i + 1).doubleValue();
|
|
|
+ double y3 = (i < n - 2) ? series.getY(i + 2).doubleValue() : series.getY(i + 1).doubleValue();
|
|
|
+
|
|
|
+ // 使用Catmull-Rom样条插值计算y值
|
|
|
+ return catmullRomSpline(x, x0, x1, x2, x3, y0, y1, y2, y3);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Catmull-Rom样条插值算法
|
|
|
+ private static double catmullRomSpline(double x, double x0, double x1, double x2, double x3,
|
|
|
+ double y0, double y1, double y2, double y3) {
|
|
|
+ // 参数化t (0到1之间)
|
|
|
+ double t = (x - x1) / (x2 - x1);
|
|
|
+
|
|
|
+ // Catmull-Rom样条公式
|
|
|
+ double t2 = t * t;
|
|
|
+ double t3 = t2 * t;
|
|
|
+
|
|
|
+ return 0.5 * ((2 * y1) +
|
|
|
+ (-y0 + y2) * t +
|
|
|
+ (2 * y0 - 5 * y1 + 4 * y2 - y3) * t2 +
|
|
|
+ (-y0 + 3 * y1 - 3 * y2 + y3) * t3);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 示例使用方法
|
|
|
+ public static void main11(String[] args) {
|
|
|
+ // 示例数据:二维数组,每行表示一个点 [含水率, 干密度]
|
|
|
+ double[][] points = {
|
|
|
+ {8.0, 1.75},
|
|
|
+ {12.0, 1.92},
|
|
|
+ {16.0, 2.05},
|
|
|
+ {20.0, 1.98},
|
|
|
+ {24.0, 1.86}
|
|
|
+ };
|
|
|
+
|
|
|
+ // 创建图表生成器
|
|
|
+ SmoothCurveChartToImage generator = new SmoothCurveChartToImage(
|
|
|
+ points,
|
|
|
+ "含水率(%)",
|
|
|
+ "干密度(g/cm³)",
|
|
|
+ "最大干密度曲线"
|
|
|
+ );
|
|
|
+
|
|
|
+ // 生成图表并保存为图片
|
|
|
+ generator.generateChart("SmoothCurveChart.png", 800, 185);
|
|
|
+ }
|
|
|
+}
|