JFreeChart 生成图表,并为图表标注特殊点、添加文本标识框

一、项目场景:

Java使用JFreeChart库生成图片,主要场景为将具体的数据 可视化 生成曲线图等的图表。

本篇文章主要针对为数据集生成的图表添加特殊点及其标识框。具体包括两种场景:x轴为 时间戳 类型和普通 数值 类型。(y轴都为数值类型)

具体的效果图如下所示:

❀ x轴为 时间戳 形式
在这里插入图片描述

❀ x轴为 数值 形式
在这里插入图片描述


二、注意事项

前提介绍
这里 标注特殊点 以及 添加文本标识框 都不算是正规的方法,但是只要注意使 用,也是十分好用的。(正规的方法也能做,估计效果不一定ok)

实现方法: 利用JFreeChart一次可以将多个数据集渲染,也就是说可以一次画多条曲线(好像这种特性是普遍都有的QAQ),将所有的特殊点作为一个统一的数据集,放在整个(数据集)集合 的末尾。让集合的其它数据集正常渲染,然后取出最后一个特殊点数据集进行特殊样式化处理。比如:只显示点、点特殊显示、在点的附近添加文本注释框。这样做的好处就是:可以非常方便的添加多个特殊点。
(前提是特殊点一定是某个数据集的点位)

注意事项
🐟 多个数据集的命名不能重复,否则会出现某个数据集的数据不能正常显示;
在这里插入图片描述
在这里插入图片描述

🐟 如果添加的文本注释框需要换行功能,可惜JFreeChart中的XYTextAnnotation并不包括这个功能,即使在文本中手动添加 '\n' 也无法实现换行。这里采用添加多个注释框,再适当的调整位置,手动实现换行(也存在弊端,当图片缩放时,多行的文本注释框的内容可能会重叠或相隔太远的问题,笔者已经试着在解决这个问题了,但是效果仍未达到完美)

🐟 为特殊点添加文本注释框时,避免不了一个问题:当特殊点出现在图表边缘位置的时候,文本显示不完全。 这里呢,已经简单的根据x轴和y轴的数据范围进行了调整,也就是下面代码中annotationXPosFormatannotationYPosFormat 方法所完成的功能。
But,解决了但没完全解决!窗口的大小也与文本框的位置调整相关,这方面我还没完善,但是如果仅需要生成一张数据可视化图片(例如:报警图),意思是不涉及图片(窗口)大小的随意变化的画,下面的代码完全是够用的了。


三、代码记录

这里直接给出所有的代码:
依赖库

<!--        JFreeChart-->
<dependency><groupId>org.jfree</groupId><artifactId>jfreechart</artifactId><version>1.5.3</version>
</dependency><!--        hutool-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version>
</dependency>

完整代码:

public class SpecialPointAnnotationFormat {public static void main(String[] args) {//创建主题样式 解决乱码(CN代表中文,这一步一定要添加)StandardChartTheme standardChartTheme = new StandardChartTheme("CN");//设置标题字体standardChartTheme.setExtraLargeFont(new Font("宋体", Font.BOLD, 15));//设置图例的字体standardChartTheme.setRegularFont(new Font("宋体", Font.PLAIN, 12));//设置轴向的字体standardChartTheme.setLargeFont(new Font("宋体", Font.BOLD, 12));//设置主题样式ChartFactory.setChartTheme(standardChartTheme);// 展示x轴为时间戳的图表
//        showXTimeSeriesChart(1600,1000);
//        showXTimeSeriesChart(1200,750);
//        showXTimeSeriesChart(800,500);
//        showXTimeSeriesChart(400,250);// 建议的图片大小showXTimeSeriesChart(1000,800);// 展示x轴为普通数值的图表
//        showXNumberSeriesChart(1000,800);}// 展示x轴为普通数值的图表private static void showXNumberSeriesChart(int width,int height){// 准备数据XYSeries xySeries = new XYSeries("Data");// 报警点double xValue = 52.15d;double yValue = 22.15d;// 手动初始化数据集int dataSize = 200;xySeries.add(0.5,-0.05);for(int i=1;i<dataSize;i++){
//        for(int i=0;i<dataSize;i++){if(i == 100){xySeries.add(xValue,yValue);continue;}xySeries.add(getRandomDouble(dataSize),getRandomDouble(dataSize));}// 整个数据集的集合seriesCollection XYSeriesCollection seriesCollection = new XYSeriesCollection();seriesCollection.addSeries(xySeries);// 创建示例数据集XYDataset dataset = seriesCollection;// 创建图表JFreeChart chart = ChartFactory.createXYLineChart("XYTextAnnotation Example","X","Y",dataset);// 获取图表的绘图区域XYPlot plot = chart.getXYPlot();// 设置曲线颜色plot.getRenderer().setSeriesPaint(0, Color.decode("#2586CC"));// 设置图表背景颜色plot.setBackgroundPaint(Color.WHITE);plot.setDomainGridlinePaint(Color.WHITE);plot.setRangeGridlinePaint(Color.WHITE);plot.setAxisOffset(new RectangleInsets(15.0, 5.0, 5.0, 5.0));plot.setRangeGridlinePaint(Color.LIGHT_GRAY);// 找到报警点对应的值Optional alarmOption = xySeries.getItems().stream().filter(obj -> {XYDataItem dataItem = (XYDataItem) obj;return NumberUtil.equals(dataItem.getXValue(), xValue) && NumberUtil.equals(dataItem.getYValue(), yValue);}).findFirst();if(alarmOption.isPresent()){XYDataItem alarmItem = (XYDataItem) alarmOption.get();addNumberSpecialPoint(plot,xySeries,alarmItem,"报警点","now","value");}else {System.out.println("未在数据集中找到报警点....");}// 找到值最大的点Optional<XYDataItem> maxOption = xySeries.getItems().stream().max(Comparator.comparingDouble(XYDataItem::getYValue));if (maxOption.isPresent()) {XYDataItem maxDataItem = maxOption.get();addNumberSpecialPoint(plot,xySeries,maxDataItem,"报警点","报警点","报警值");}// 找到值最小的点Optional<XYDataItem> minOption = xySeries.getItems().stream().min(Comparator.comparingDouble(XYDataItem::getYValue));if (minOption.isPresent()) {XYDataItem minDataItem = minOption.get();addNumberSpecialPoint(plot,xySeries,minDataItem,"报警点","报警点","报警值");}// 创建图表窗口并显示图表ChartFrame frame = new ChartFrame("x轴为数值类型的曲线图", chart);frame.setPreferredSize(new Dimension(width, height));frame.pack();frame.setVisible(true);}// 展示x轴为时间戳的图表private static void showXTimeSeriesChart(int width,int height){LocalDateTime alarmTime = LocalDateTime.now();LocalDateTime dateTime = alarmTime.minusMinutes(5l).minusSeconds(30l);TimeSeries series = new TimeSeries("Data");// 手动初始化数据集int num = 200;series.add(new Millisecond(Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant())),-12.5d);
//        for(int i=1;i<num;i++){
//        for(int i=0;i<num;i++){for(int i=1;i<num-1;i++){series.add(new Millisecond(Date.from(dateTime.plusSeconds((long)3*i).atZone(ZoneId.systemDefault()).toInstant())),getRandomDouble(num));}series.add(new Millisecond(Date.from(dateTime.plusSeconds((long)3*(num-1)).atZone(ZoneId.systemDefault()).toInstant())),num + 21.5);// 整个数据集的集合seriesCollection TimeSeriesCollection seriesCollection = new TimeSeriesCollection();seriesCollection.addSeries(series);// 创建示例数据集XYDataset dataset = seriesCollection;// 创建曲线图JFreeChart chart = ChartFactory.createTimeSeriesChart("", // 图表标题"", // X轴标签"", // Y轴标签dataset // 数据集);// 获取图表的绘图区域XYPlot plot = chart.getXYPlot();// 设置曲线颜色plot.getRenderer().setSeriesPaint(0, Color.decode("#2586CC"));// 设置图表背景颜色plot.setBackgroundPaint(Color.WHITE);plot.setDomainGridlinePaint(Color.WHITE);plot.setRangeGridlinePaint(Color.WHITE);plot.setAxisOffset(new RectangleInsets(15.0, 5.0, 5.0, 5.0));plot.setRangeGridlinePaint(Color.LIGHT_GRAY);// 找到报警点对应的值// 将报警时间转换为毫秒表示long alarmTimeMillis = alarmTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();Optional alarmOptional = series.getItems().stream().filter(obj -> {long firstMillisecond = ((TimeSeriesDataItem) obj).getPeriod().getFirstMillisecond();return firstMillisecond == alarmTimeMillis;}).findFirst();if(alarmOptional.isPresent()){TimeSeriesDataItem alarmItem = (TimeSeriesDataItem) alarmOptional.get();addTimeSpecialPoint(plot,series,alarmItem,"报警点", "now","value");}else {System.out.println("未在数据集中找到报警点....");}// 找到值最大的点(一般值最大的点为报警点)Optional<TimeSeriesDataItem> maxOption = series.getItems().stream().max(Comparator.comparingDouble(item -> ((TimeSeriesDataItem) item).getValue().doubleValue()));if (maxOption.isPresent()) {TimeSeriesDataItem maxDataItem = maxOption.get();addTimeSpecialPoint(plot,series,maxDataItem,"报警点","报警时间","报警点");}// 找到值最小的点(一般值最大的点为报警点)Optional<TimeSeriesDataItem> minOption = series.getItems().stream().min(Comparator.comparingDouble(item -> ((TimeSeriesDataItem) item).getValue().doubleValue()));if (minOption.isPresent()) {TimeSeriesDataItem minDataItem = minOption.get();addTimeSpecialPoint(plot,series,minDataItem,"报警点","报警时间","报警点");}double xRange = plot.getDomainAxis().getRange().getLength();double yRange = plot.getRangeAxis().getRange().getLength();// 创建图表窗口并显示图表ChartFrame frame = new ChartFrame("x轴为时间戳类型的曲线图", chart);frame.setPreferredSize(new Dimension(width,height));frame.pack();// pack会默认渲染为frame的最佳尺寸frame.setVisible(true);// 这里也可以直接生成一张图片存放到指定位置(path)// 生成一张图片
//        try {
//            ByteArrayOutputStream out = new ByteArrayOutputStream();
//            ChartUtils.writeChartAsJPEG(out, chart, 1000, 800);
//            String path = System.getProperty("user.dir") + "\\images\\image.jpg";
//
//            downloadByteArrayOutputStream(out.toByteArray(), path);
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }}public static void downloadByteArrayOutputStream(byte[] data, String outputPath) {try {ByteArrayInputStream inputStream = new ByteArrayInputStream(data);FileOutputStream outputStream = new FileOutputStream(outputPath);// 将字节数组写入到文件byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, bytesRead);}// 关闭流inputStream.close();outputStream.close();System.out.println("文件下载完成:" + outputPath);} catch (IOException e) {e.printStackTrace();}}/*** 图表添加特殊点(x轴为数值类型:double)* @param plot 图层* @param xySeries 一个chart可以有多个数据集(多条折线),需要标识为哪个数据集添加特殊点* @param dataItem 需要标识的点* @param specialTextTitle 特殊点集合名称标识(可置为“”,注意不同数据集的名称不可重复)* @param xSpecialText 特殊点对应的x轴的提示内容* @param ySpecialText 特殊点对应的y轴的提示内容*/private static void addNumberSpecialPoint(XYPlot plot,XYSeries xySeries ,XYDataItem dataItem,String specialTextTitle,String xSpecialText,String ySpecialText){XYSeriesCollection seriesCollection = (XYSeriesCollection) plot.getDataset();XYItemRenderer r = plot.getRenderer();XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;double xValue = dataItem.getXValue();double yValue = dataItem.getYValue();// 设置特殊点的集合// 判断特殊点集合之前是否已创建XYSeries specialSeries = null;int seriesSize = seriesCollection.getSeries().size();if(seriesSize > 1){specialSeries = (XYSeries)seriesCollection.getSeries().get(seriesSize-1);// 再判断特殊点是否已添加Optional optional = specialSeries.getItems().stream().filter(item -> {XYDataItem xyDataItem = (XYDataItem) item;return NumberUtil.equals(xValue, xyDataItem.getXValue()) && NumberUtil.equals(yValue, xyDataItem.getYValue());}).findFirst();if(optional.isPresent()){// 特殊点已经添加return;}}else {specialSeries = new XYSeries(specialTextTitle);seriesCollection.addSeries(specialSeries);}// 添加特殊值specialSeries.add(xValue,yValue);// 格式// 设置文本颜色和透明度Color textColor = new Color(255, 0, 0, 128); // 设置为半透明的红色(透明度为 128)Font font = new Font("SansSerif", Font.BOLD, 12);// 创建一个带文字的注释框String alarmText1 = xSpecialText + ":" + xValue;XYTextAnnotation line1 = new XYTextAnnotation(alarmText1,xValue,yValue);line1.setFont(font);line1.setPaint(textColor);line1.setX((double) annotationXPosFormat(plot,xySeries,xValue, TextAnnotationTypeEnum.DOUBLE.getType()));line1.setY(annotationYPosFormat(plot,xySeries,yValue,TextAnnotationTypeEnum.DOUBLE.getType()));plot.addAnnotation(line1);double lineSpace = 3.0; // 3Optional<XYDataItem> maxOption = xySeries.getItems().stream().max(Comparator.comparingDouble(XYDataItem::getYValue));Optional<XYDataItem> minOption = xySeries.getItems().stream().min(Comparator.comparingDouble(XYDataItem::getYValue));if(maxOption.isPresent() && minOption.isPresent()) {double sub = maxOption.get().getYValue() - minOption.get().getYValue();lineSpace = sub/50;}String alarmText2 = ySpecialText + ":" + yValue;XYTextAnnotation line2 = new XYTextAnnotation(alarmText2,xValue,yValue);line2.setFont(font);line2.setPaint(textColor);line2.setX((double)annotationXPosFormat(plot,xySeries,xValue,TextAnnotationTypeEnum.DOUBLE.getType()));line2.setY(annotationYPosFormat(plot,xySeries,yValue,TextAnnotationTypeEnum.DOUBLE.getType())-lineSpace);plot.addAnnotation(line2);int pointSize = 3;Shape specifiedShape = new Ellipse2D.Double(-(pointSize*2), -(pointSize*2), pointSize*2, pointSize*2); // 指定点显示的形状Paint specifiedPaint = Color.RED; // 指定点显示的颜色// 对某个指定序列集合的点进行操作renderer.setSeriesShapesVisible(seriesCollection.getSeriesCount() - 1, true); // 显示指定点的形状renderer.setSeriesShape(seriesCollection.getSeriesCount() - 1, specifiedShape); // 设置指定点的形状renderer.setSeriesPaint(seriesCollection.getSeriesCount() - 1, specifiedPaint); // 设置指定点的颜色// 特殊点只显示点,而不显示曲线renderer.setSeriesLinesVisible(seriesCollection.getSeriesCount() - 1, false); // 不显示曲线renderer.setSeriesShapesVisible(seriesCollection.getSeriesCount() - 1, true); // 显示点}/*** 图表添加特殊点(x轴为时间戳类型)* @param plot 图层* @param timeSeries 一个chart可以有多个数据集(多条折线),需要标识为哪个数据集添加特殊点* @param dataItem 需要标识的点* @param specialTextTitle 特殊点集合名称标识(可置为“”,注意不同数据集的名称不可重复)* @param xSpecialText 特殊点对应的x轴的提示内容* @param ySpecialText 特殊点对应的y轴的提示内容*/private static void addTimeSpecialPoint(XYPlot plot,TimeSeries timeSeries ,TimeSeriesDataItem dataItem,String specialTextTitle,String xSpecialText,String ySpecialText){TimeSeriesCollection seriesCollection = (TimeSeriesCollection) plot.getDataset();XYItemRenderer r = plot.getRenderer();XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;long xValue = dataItem.getPeriod().getFirstMillisecond();double yValue = dataItem.getValue().doubleValue();// 设置特殊点的集合// 判断特殊点集合之前是否已创建TimeSeries specialSeries = null;int seriesSize = seriesCollection.getSeries().size();if(seriesSize > 1){specialSeries = (TimeSeries)seriesCollection.getSeries().get(seriesSize-1);// 再判断特殊点是否已添加Optional optional = specialSeries.getItems().stream().filter(item -> {TimeSeriesDataItem timeDataItem = (TimeSeriesDataItem) item;return NumberUtil.equals(xValue, timeDataItem.getPeriod().getFirstMillisecond()) && NumberUtil.equals(yValue, timeDataItem.getValue().doubleValue());}).findFirst();if(optional.isPresent()){// 特殊点已经添加return;}}else {specialSeries = new TimeSeries(specialTextTitle);seriesCollection.addSeries(specialSeries);}// 添加特殊值specialSeries.add(dataItem.getPeriod(),dataItem.getValue().doubleValue());// 格式// 设置文本颜色和透明度Color textColor = new Color(255, 0, 0, 128); // 设置为半透明的红色(透明度为 128)Font font = new Font("SansSerif", Font.BOLD, 12);// 创建一个带文字的注释框String alarmText1 = ySpecialText + ":" + yValue;XYTextAnnotation line1 = new XYTextAnnotation(alarmText1,xValue,yValue);line1.setFont(font);line1.setPaint(textColor);line1.setX((long)annotationXPosFormat(plot,timeSeries,xValue, TextAnnotationTypeEnum.TIME.getType()));line1.setY(annotationYPosFormat(plot,timeSeries,yValue,TextAnnotationTypeEnum.TIME.getType()));plot.addAnnotation(line1);/*** 通过获取font获取一行字的行高*/
//        // 设置XYTextAnnotation之间的行间距
//        // 获取font的行高
//        FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
//        int lineHeight = fontMetrics.getHeight();
//        // 250->1.2 | 500->0.6 | 750->0.3 | 1000->0.2
//        double lineSpace = lineHeight + xx;
//        System.out.println("lineSpace=" + lineSpace);
//        System.out.println("xx=" + xx);// 设置换行的间隔double lineSpace = 3.0; // 3Optional<TimeSeriesDataItem> maxOption = timeSeries.getItems().stream().max(Comparator.comparingDouble(item -> ((TimeSeriesDataItem) item).getValue().doubleValue()));Optional<TimeSeriesDataItem> minOption = timeSeries.getItems().stream().min(Comparator.comparingDouble(item -> ((TimeSeriesDataItem) item).getValue().doubleValue()));if(maxOption.isPresent() && minOption.isPresent()) {double sub = maxOption.get().getValue().doubleValue() - minOption.get().getValue().doubleValue();lineSpace = sub/50;}String alarmText2 = xSpecialText + ":" + LocalDateTime.ofInstant(Instant.ofEpochMilli(xValue), ZoneId.systemDefault()).format(DatePattern.NORM_TIME_FORMATTER);XYTextAnnotation line2 = new XYTextAnnotation(alarmText2,xValue,yValue);line2.setFont(font);line2.setPaint(textColor);line2.setX((long)annotationXPosFormat(plot,timeSeries,xValue,TextAnnotationTypeEnum.TIME.getType()));line2.setY(annotationYPosFormat(plot,timeSeries,yValue,TextAnnotationTypeEnum.TIME.getType())-lineSpace);plot.addAnnotation(line2);int dotSize = 3;Shape specifiedShape = new Ellipse2D.Double(-(dotSize*2), -(dotSize*2), dotSize*2, dotSize*2); // 指定点显示的形状Paint specifiedPaint = Color.RED; // 指定点显示的颜色// 对特殊点集合的点进行操作()renderer.setSeriesShapesVisible(seriesCollection.getSeriesCount() - 1, true); // 显示指定点的形状renderer.setSeriesShape(seriesCollection.getSeriesCount() - 1, specifiedShape); // 设置指定点的形状renderer.setSeriesPaint(seriesCollection.getSeriesCount() - 1, specifiedPaint); // 设置指定点的颜色// 特殊点只显示点,而不显示曲线renderer.setSeriesLinesVisible(seriesCollection.getSeriesCount() - 1, false); // 不显示曲线renderer.setSeriesShapesVisible(seriesCollection.getSeriesCount() - 1, true); // 显示点}/*** XYTextAnnotation 文本框位置(x)调整(防止文本框处于边缘位置导致的文本显示不全)* @param series 数据集合* @param value x轴的值* @param type x轴值的类型(有时间戳类型[long]和数值类型[double])* @return 返回值与type的入参类型相同(方法调用处需要类型转换)*/private static Object annotationXPosFormat(XYPlot plot,Series series, Object value, String type){// x轴的范围double xRange = plot.getDomainAxis().getRange().getLength();double offset = xRange * 0.05;if(TextAnnotationTypeEnum.TIME.getType().equals(type)){// x轴为时间戳形式long xValue = (long) value;TimeSeries timeSeries = (TimeSeries) series;int size = timeSeries.getItems().size();long maxValue = timeSeries.getDataItem(size - 1).getPeriod().getFirstMillisecond();long minValue = timeSeries.getDataItem(0).getPeriod().getFirstMillisecond();if(xValue - minValue <= offset){return xValue + (long)offset;}if(maxValue - xValue <= offset){return xValue - (long)offset;}return xValue;}else if(TextAnnotationTypeEnum.DOUBLE.getType().equals(type)){// x轴为double形式double xValue = (double) value;XYSeries xySeries = (XYSeries) series;Optional<XYDataItem> maxOption = xySeries.getItems().stream().max(Comparator.comparingDouble(XYDataItem::getXValue));Optional<XYDataItem> minOption = xySeries.getItems().stream().min(Comparator.comparingDouble(XYDataItem::getXValue));if(minOption.isPresent() && xValue - minOption.get().getXValue() <= offset){return xValue + offset;}if(maxOption.isPresent() && maxOption.get().getXValue() - xValue <= offset){return xValue - offset;}return xValue;}else {return value;}}/***  XYTextAnnotation 文本框位置(y)调整(防止文本框处于边缘位置导致的文本显示不全)* @param series 数据集合* @param yValue y轴的值* @param type x轴值的类型(有时间戳类型[long]和数值类型[double])* @return 统一为double*/private static double annotationYPosFormat(XYPlot plot ,Series series,double yValue,String type){// y轴值的范围double yRange = plot.getRangeAxis().getRange().getLength();double offset = yRange * 0.05;// y轴一般都为double类型if(TextAnnotationTypeEnum.TIME.getType().equals(type)){TimeSeries timeSeries = (TimeSeries) series;Optional<TimeSeriesDataItem> maxOption = timeSeries.getItems().stream().max(Comparator.comparingDouble(item -> ((TimeSeriesDataItem) item).getValue().doubleValue()));Optional<TimeSeriesDataItem> minOption = timeSeries.getItems().stream().min(Comparator.comparingDouble(item -> ((TimeSeriesDataItem) item).getValue().doubleValue()));if(maxOption.isPresent() && minOption.isPresent()){double minValue = minOption.get().getValue().doubleValue();double maxValue = maxOption.get().getValue().doubleValue();if(minOption.isPresent() && yValue - minValue <= offset){return yValue + offset;}if(maxOption.isPresent() && maxValue - yValue <= offset){return yValue - offset/3;}return yValue - offset/3;}return yValue;}else if(TextAnnotationTypeEnum.DOUBLE.getType().equals(type)){XYSeries xySeries = (XYSeries) series;Optional<XYDataItem> maxOption = xySeries.getItems().stream().max(Comparator.comparingDouble(XYDataItem::getYValue));Optional<XYDataItem> minOption = xySeries.getItems().stream().min(Comparator.comparingDouble(XYDataItem::getYValue));if(maxOption.isPresent() && minOption.isPresent()){if(minOption.isPresent() && yValue - minOption.get().getYValue() <= offset){return yValue + offset;}if(maxOption.isPresent() && maxOption.get().getYValue() - yValue <= offset){return yValue - offset/3;}return yValue - offset/3;}return yValue;}else {return yValue;}}/*** 获取指定范围的double类型的随机数* @param scale 范围*/private static Double getRandomDouble(Integer scale){Random random = new Random();double randomNumber = random.nextDouble() * scale; // 生成0到100之间的随机小数BigDecimal bigDecimal = BigDecimal.valueOf(randomNumber).setScale(2, RoundingMode.HALF_DOWN);return bigDecimal.doubleValue();}
}



🐟
bye!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/222208.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

maven限制内存使用峰值/最大内存

前言 通过设置虚拟机的内存大小&#xff0c;达到限制maven内存使用峰值的效果 方法1&#xff1a;修改mvn脚本 找到mvn脚本在MAVEN_OPTS参数值添加-Xms、-Xmx参数&#xff1a;MAVEN_OPTS"$MAVEN_OPTS -Xms512m -Xmx512m"效果图 windows系统下修改MAVEN_OPTS参数 …

解锁高效工作!5款优秀工时管理软件推荐

工时管理&#xff0c;一直是让许多企业和团队头疼的问题。传统的纸质工时表、复杂的电子表格&#xff0c;不仅操作繁琐&#xff0c;还容易出错。幸好&#xff0c;随着科技的进步&#xff0c;我们迎来了工时管理软件的春天。今天&#xff0c;就让我们一起走进这个新时代&#xf…

iconfont拓展iview图标库

文章目录 前言1.如何下载图标&#xff1f;首先进入官网&#xff0c;找属于自己需求的图标&#xff0c;添加到购物车2.点击右上角的购物车&#xff0c;会来到此页面&#xff0c;点击下载代码3.下载完成后&#xff0c;是一个压缩包&#xff0c;文件内容如下4.在项目assets下新建一…

数据结构和算法笔记2:二分法

二分法网上有两种写法&#xff0c;一种左闭右闭&#xff0c;一种左闭右开&#xff0c;个人习惯左闭右闭的写法&#xff0c; 有序数组查找数 这是标准二分法&#xff0c;对应力扣的704. 二分查找&#xff1a; 求值为target的索引 int search(vector<int>& nums, i…

关于时区处理策略

前端会通过 App-Id 请求头附带 客户端时区 信息 前端传入的如果是 字符串&#xff0c;会自动根据 请求的客户端时区 解析为对应的 日期 如果前端传入的是时间戳&#xff0c;则无需额外解析转换 如果是 商户后台、管理后台 都统一基于 商户所在国家的时区&#xff08;总台目前…

防火墙安全策略

目录 一、防火墙种类 二、防火墙流量控制手段 1、包过滤技术&#xff08;传统&#xff09; 2、状态检测技术 &#xff08;1&#xff09;、状态检测机制 三、安全实验 1、拓扑 2、需求 3、配置思路 4、关键配置截图 5、验证 一、防火墙种类 对于防火墙来说就是针对哪…

COSP营造户外骑行新业态:2024深圳户外骑行展在深圳福田召开

随着人们生活水平的提高&#xff0c;绿色健康的生活方式备受关注&#xff0c;户外骑行作为一种绿色、健康的运动方式&#xff0c;越来越受到人们的青睐。此次展会将展示各种类型的户外骑行装备&#xff0c;包括自行车、头盔、手套、鞋类、骑行服等。深圳是中国户外骑行展会的重…

最新国内免费使用GPT4教程,GPT语音对话使用,Midjourney绘画

一、前言 ChatGPT3.5、GPT4.0、GPT语音对话、Midjourney绘画&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和用户进行创作交流。 然而&#xff0c;GP…

油猴脚本教程案例【键盘监听】-编写 ChatGPT 快捷键优化

文章目录 1. 元数据1. name2. namespace3. version4. description5. author6. match7. grant8. icon 2. 编写函数.1 函数功能2.1.1. input - 聚焦发言框2.1.2. stop - 取消回答2.1.3. newFunction - 开启新窗口2.1.4. scroll - 回到底部 3. 监听键盘事件3.1 监听X - 开启新对话…

随机问卷调查数据的处理(uniapp)

需求&#xff1a;问卷调查 1.返回的数据中包含单选、多选、多项文本框、单文本框、图片上传 2.需要对必填的选项进行校验 3.非必填的多项文本框内容 如果不填写 不提交 表单数据格式 res{"code": 0,"msg": null,"data": [{"executeDay&…

持续集成交付CICD:HELM 手动完成前端项目应用发布与回滚

目录 一、实验 1.环境 2.K8S master节点部署HELM3 3.K8S master节点安装git 4. Harbor镜像确认 5. HELM 手动完成前端项目应用发布与回滚 6.代码上传到GitLab 二、问题 1.Ingress中 path 的类型有何区别 2. HELM创建项目报错 一、实验 1.环境 &#xff08;1&#x…

Flink cdc3.0同步实例(动态变更表结构、分库分表同步)

文章目录 前言准备flink环境docker构建mysql、doris环境数据准备 通过 FlinkCDC cli 提交任务整库同步同步变更路由变更路由表结构不一致无法同步 结尾 前言 最近Flink CDC 3.0发布&#xff0c; 不仅提供基础的数据同步能力。schema 变更自动同步、整库同步、分库分表等增强功…

MyBatis:动态 SQL 标签

MyBatis 动态 SQL 标签if 标签where 标签trim 标签choose 、when 、otherwise 标签foreach 标签附 动态 SQL 标签 MyBatis 动态 SQL 标签&#xff0c;是一组预定义的标签&#xff0c;用于构建动态的 SQL 语句&#xff0c;允许在 SQL 语句中使用条件、循环和迭代等逻辑。通过使…

HW03 -实物图像识别-改进:图像增强、网络架构,K折交叉验证

修改模型架构或者进行图像增强 # Normally, We dont need augmentations in testing and validation. # All we need here is to resize the PIL image and transform it into Tensor. test_tfm transforms.Compose([transforms.Resize((128, 128)),transforms.ToTensor(),#t…

Vue中的加密方式(js-base64、crypto-js、jsencrypt、bcryptjs)

目录 1.安装js-base64库 2. 在Vue组件中引入js-base64库 3.使用js-base64库进行加密 4.Vue中其他加密方式 1.crypto-js 2.jsencrypt 3.bcryptjs 1.安装js-base64库 npm install js-base64 --save-dev 2. 在Vue组件中引入js-base64库 import { Base64 } from js-ba…

大型语言模型:SBERT — Sentence-BERT

slavahead 一、介绍 Transformer 在 NLP 方面取得了进化进步&#xff0c;这已经不是什么秘密了。基于转换器&#xff0c;许多其他机器学习模型已经发展起来。其中之一是BERT&#xff0c;它主要由几个堆叠的变压器编码器组成。除了用于情感分析或问答等一系列不同的问题外&#…

AI数字人互动大屏支持多种场景交互!

互动大屏&#xff08;技术支持&#xff1a;zhibo175&#xff09;本身具有令人瞩目的效果&#xff0c;再配置丰富多彩的多媒体&#xff0c;如引人注目的广告、特效或游戏等&#xff0c;可起到很好的引流作用。在空间开阔且客流密集的场所&#xff0c;使用各种形态的大面积屏幕&a…

【AIGC重塑教育】AI大模型驱动的教育变革与实践

文章目录 &#x1f354;现状&#x1f6f8;解决方法✨为什么要使用ai&#x1f386;彩蛋 &#x1f354;现状 AI正迅猛地改变着我们的生活。根据高盛发布的一份报告&#xff0c;AI有可能取代3亿个全职工作岗位&#xff0c;影响全球18%的工作岗位。在欧美&#xff0c;或许四分之一…

【2023 英特尔On技术创新大会直播 |我与英特尔的初次相遇】—— AIPC探索下一代的物联网时代

&#x1f308;个人主页: Aileen_0v0 &#x1f525;系列专栏:英特尔技术学习专栏 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 硅谷经济的发展与挑战 Intel开发者云与AI技术的应用 AI压缩技术的发展与应用 英特尔与阿里巴巴在AI领域的合作 AIPC时代的…

flink sql1.18.0连接SASL_PLAINTEXT认证的kafka3.3.1

阅读此文默认读者对docker、docker-compose有一定了解。 环境 docker-compose运行了一个jobmanager、一个taskmanager和一个sql-client。 如下&#xff1a; version: "2.2" services:jobmanager:image: flink:1.18.0-scala_2.12container_name: jobmanagerports:…