当前位置: 首页 > news >正文

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!

相关文章:

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

一、项目场景&#xff1a; Java使用JFreeChart库生成图片&#xff0c;主要场景为将具体的数据 可视化 生成曲线图等的图表。 本篇文章主要针对为数据集生成的图表添加特殊点及其标识框。具体包括两种场景&#xff1a;x轴为 时间戳 类型和普通 数值 类型。&#xff08;y轴都为…...

vue整合axios 未完

一、简介 1、介绍 axios前端异步请求库类似jouery ajax技术&#xff0c;axios用来在前端页面发起一个异步请求&#xff0c;请求之后页面不动&#xff0c;响应回来刷新页面局部&#xff1b;Axios 是一个基于 promise 的 HTTP 库&#xff0c;可以用在浏览器和 node.js 中 2、特…...

java代码编写twitter授权登录

在上一篇内容已经介绍了怎么申请twitter开放的API接口。 下面介绍怎么通过twitter提供的API&#xff0c;进行授权登录功能。 开发者页面设置 首先在开发者页面开启“用户认证设置”&#xff0c;点击edit进行信息编辑。 我的授权登录是个网页&#xff0c;并且只需要进行简单的…...

​ SK Ecoplant借助亚马逊云科技,海外服务器为环保事业注入新活力

在当今全球面临着资源紧缺和环境挑战的大背景下&#xff0c;数字技术所依赖的海外服务器正成为加速循环经济转型的关键利器。然而&#xff0c;很多企业在整合数字技术到运营中仍然面临着一系列挑战&#xff0c;依然存在低效流程导致的不必要浪费。针对这一问题&#xff0c;SK E…...

RPC(5):AJAX跨域请求处理

接上一篇RPC&#xff08;4&#xff09;&#xff1a;HttpClient实现RPC之POST请求进行修改。 1 修改客户端项目 1.1 修改maven文件 修改后配置文件如下&#xff1a; <dependencyManagement><dependencies><dependency><groupId>org.springframework.b…...

用大白话举例子讲明白区块链

什么是区块链&#xff1f;网上这么说&#xff1a; 区块链是一种分布式数据库技术&#xff0c;它以块的形式记录和存储交易数据&#xff0c;并使用密码学算法保证数据的安全性和不可篡改性。每个块都包含了前一个块的哈希值和自身的交易数据&#xff0c;形成了一个不断增长的链条…...

Java URL

URL&#xff1a;统一资源定位符&#xff0c;说白了&#xff0c;就是一个网络 通过URLConnection类可以连接到URL&#xff0c;然后通过URLConnection可以获取读数据的通道。非文本数据用字节流来读取。 读完之后写入本地即可。 public class test {public static void main(S…...

ETL-从1学到100(1/100):ETL涉及到的名词解释

本文章主要介绍ETL和大数据中涉及到名词&#xff0c;同时解释这些名词的含义。由于不是一次性收集这些名词&#xff0c;所以这篇文章将会持续更新&#xff0c;更新日志会存放在本段话下面&#xff1a; 12-19更新&#xff1a;OLTP、OLAP、BI、ETL。 12-20更新&#xff1a;ELT、…...

Jenkins + gitlab 持续集成和持续部署的学习笔记

1. Jenkins 介绍 软件开发生命周期(SLDC, Software Development Life Cycle)&#xff1a;它集合了计划、开发、测试、部署的集合。 软件开发瀑布模型 软件的敏捷开发 1.1 持续集成 持续集成 (Continuous integration 简称 CI): 指的是频繁的将代码集成到主干。 持续集成的流…...

R语言【cli】——通过cli_abort用 cli 格式的内容显示错误、警告或信息,内部调用cli_bullets和inline-makeup

cli_abort(message,...,call .envir,.envir parent.frame(),.frame .envir ) 先从那些不需要下大力气理解的参数入手&#xff1a; 参数【.envir】&#xff1a;进行万能表达式编译的环境。 参数【.frame】&#xff1a;抛出上下文。默认用于参数【.trace_bottom】&#xff…...

cka从入门到放弃

无数次想放弃&#xff0c;最后选择了坚持 监控pod日志 监控名为 foobar 的 Pod 的日志&#xff0c;并过滤出具有 unable-access-website 信息的行&#xff0c;然后将 写入到 /opt/KUTR00101/foobar # 解析 监控pod的日志&#xff0c;使用kubectl logs pod-name kubectl logs…...

通过 jekyll 构建 github pages 博客实战笔记

jekyll 搭建教程 jekyll 搭建教程 Gem 安装 Ruby&#xff0c;请访问 下载地址。 Jekyll Jekyll 是一个简单且具备博客特性的静态网站生成器。 Jekyll 中文文档 极客学院中文文档 使用以下命令安装 Jekyll。 $ gem install jekyll在中国可能需要使用代理软件。然后&#xff…...

【AI美图】第09期效果图,AI人工智能汽车+摩托车系列图集

期待中的未来AI汽车 欢迎来到未来的世界&#xff0c;一个充满创新和无限可能的世界&#xff0c;这里有你从未见过的科技奇迹——AI汽车。 想象一下&#xff0c;你站在十字路口&#xff0c;繁忙的交通信号灯在你的视线中闪烁&#xff0c;汽车如潮水般涌来&#xff0c;但是&…...

网线的制作集线器交换机路由器的配置--含思维导图

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《产品经理如何画泳道图&流程图》 ⛺️ 越努力 &#xff0c;越幸运 一、网线的制作 1、网线的材料有哪些&#xff1f; 网线 网线是一种用于传输数据信号的电缆&#xff0c;广泛应…...

LLM微调(四)| 微调Llama 2实现Text-to-SQL,并使用LlamaIndex在数据库上进行推理

Llama 2是开源LLM发展的一个巨大里程碑。最大模型及其经过微调的变体位居Hugging Face Open LLM排行榜&#xff08;https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard&#xff09;前列。多个基准测试表明&#xff0c;就性能而言&#xff0c;它正在接近GPT-3.5…...

柔性数组(结构体成员)

目录 前言&#xff1a; 柔性数组&#xff1a; 给柔性数组分配空间&#xff1a; 调整柔性数组大小&#xff1a; 柔性数组的好处&#xff1a; 前言&#xff1a; 柔性数组&#xff1f;可能你从未听说&#xff0c;但是确实有这个概念。听名字&#xff0c;好像就是柔软的数…...

C#合并多个Word文档(微软官方免费openxml接口)

g /// <summary>/// 合并多个word文档&#xff08;合并到第一文件&#xff09;/// </summary>/// <param name"as_word_paths">word文档完整路径</param>/// <param name"breakNewPage">true(默认值)&#xff0c;合并下一个…...

MySQL 5.7依赖的软件包和下载地址

​​​​​​​yum install ncurses-devel openssl openssl-devel gcc gcc-c ncurses ncurses-devel bison make -y mysql下载地址 下载地址...

图论 | 网络流的基本概念

文章目录 流网路残留网络增广路径割最大流最小割定理最大流Edmonds-Karp 算法算法步骤程序代码时间复杂度 流网路 流网络&#xff1a; G ( V , E ) G (V, E) G(V,E) 有向图&#xff0c;不考虑反向边s&#xff1a;源点t&#xff1a;汇点 c ( u , v ) c(u, v) c(u,v)&#xff…...

【音视频 | AAC】AAC音频编码详解

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

redis基本用法学习(C#调用NRedisStack操作redis)

redis官网文档中推荐C#中使用NRedisStack包连接并操作redis&#xff0c;本文学习C#调用NRedisStack操作redis的基本方式。   新建Winform项目&#xff0c;在Nuget包管理器中搜索并安装NRedisStack包&#xff0c;如下图所示&#xff1a; 主要调用StackExchange.Redis命名空间下…...

[CVPR 2023:3D Gaussian Splatting:实时的神经场渲染]

文章目录 前言小结 原文地址&#xff1a;https://blog.csdn.net/qq_45752541/article/details/132854115 前言 mesh 和点是最常见的3D场景表示&#xff0c;因为它们是显式的&#xff0c;非常适合于快速的基于GPU/CUDA的栅格化。相比之下&#xff0c;最近的神经辐射场&#xf…...

【SpringBoot快速入门】(4)SpringBoot项目案例代码示例

目录 1 创建工程3 配置文件4 静态资源 之前我们已经学习的Spring、SpringMVC、Mabatis、Maven&#xff0c;详细讲解了Spring、SpringMVC、Mabatis整合SSM的方案和案例&#xff0c;上一节我们学习了SpringBoot的开发步骤、工程构建方法以及工程的快速启动&#xff0c;从这一节开…...

Linux服务器 部署飞书信息发送服务

项目介绍&#xff1a; 飞书信息发送服务是指将飞书信息发送服务部署到一个Linux服务器上。飞书是一款企业级的即时通讯和协作工具&#xff0c;支持发送消息给飞书的功能。通过部署飞书信息发送服务&#xff0c;可以方便内网发送信息给外网飞书。 项目代码结构展示&#xff1a; …...

用C#也能做机器学习?

前言✨ 说到机器学习&#xff0c;大家可能都不陌生&#xff0c;但是用C#来做机器学习&#xff0c;可能很多人还第一次听说。其实在C#中基于ML.NET也是可以做机器学习的&#xff0c;这种方式比较适合.NET程序员在项目中集成机器学习模型&#xff0c;不太适合专门学习机器学习&a…...

Python PDF格式转PPT格式

要将PDF文件转换为PPT&#xff0c;我实在python3.9 环境下转成功的&#xff0c;python3.11不行。 需要 pip install PyMuPDF代码说话 # -*- coding: utf-8 -*-""" author: 赫凯 software: PyCharm file: xxx.py time: 2023/12/21 11:20 """im…...

搭建知识付费平台?明理信息科技为你提供全程解决方案

明理信息科技saas知识付费平台 在当今数字化时代&#xff0c;知识付费已经成为一种趋势&#xff0c;越来越多的人愿意为有价值的知识付费。然而&#xff0c;公共知识付费平台虽然内容丰富&#xff0c;但难以满足个人或企业个性化的需求和品牌打造。同时&#xff0c;开发和维护…...

漫谈UNIX、Linux、UNIX-Like

漫谈UNIX、Linux、UNIX-Like 使用了这么多年Redhat、Ubuntu等Linux、Windows、Solaris操作系统&#xff0c;你是否对UNIX、Unix-Like&#xff08;类UNIX&#xff09;还是不太清楚&#xff1f;我以前一直认为Unix-Like就等于Linux。其实&#xff0c;由UNIX派生出来而没有取得UN…...

Netty Review - Netty与Protostuff:打造高效的网络通信

文章目录 概念PrePomServer & ClientProtostuffUtil 解读测试小结 概念 Pre 每日一博 - Protobuf vs. Protostuff&#xff1a;性能、易用性和适用场景分析 Pom <dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-…...

在ClickHouse数据库中启用预测功能

在这篇博文中&#xff0c;我们将介绍如何将机器学习支持的预测功能与 ClickHouse 数据库集成。ClickHouse 是一个快速、开源、面向列的 SQL 数据库&#xff0c;对于数据分析和实时分析非常有用。该项目由 ClickHouse&#xff0c; Inc. 维护和支持。我们将探索它在需要数据准备以…...

网站诊断方案/学校seo推广培训班

Redhat Linux网卡及IP设置单网卡多IP例&#xff1a;将eht0 添加新地址 eth0:1拷贝/etc/sysconfig/network-scripts/ifcfg-eth0文件为ifcfg-eth0:1修改其中DEVICEeth0:1根据需要修改IP地址(IPADD)和掩码(NETMASK),可以删除NETWORK、BROADCAST、HWADDR重启网络服务#service netwo…...

日本室内设计网站推荐/关键词推广优化

在放映电影的过程中&#xff0c;画面被一幅幅地放映在银幕上。画幅移开时&#xff0c;光线就被遮住&#xff0c;幕上便出现短暂的黑暗&#xff1b;每放映一个画幅后&#xff0c;幕上就黑暗一次。但这一次次极短暂的黑暗&#xff0c;被人的视觉生理现象“视觉暂留”所弥补。人眼…...

开锁公司网站模板/广东seo网站推广

之前七娃&#xff0c;整理过用css实现鼠标左右键禁用&#xff1a;静态页面js防止抽离 今天新增一个通过css设置body的样式&#xff0c;将鼠标左键禁用了&#xff0c;禁止用户选择&#xff01; body{-webkit-touch-callout: none;-webkit-user-select: none;-khtml-user-selec…...

三亚疫情最新政策/关键词优化是怎么弄的

const iterator表示iterator是const的&#xff0c;即iterator本身不能改变或者说iterator指针不能改变&#xff0c;但是iterator所指向的内容可以改变。比如&#xff0c; const std::vector<int>::iterator iter vec.begin(); *iter 10;是对的&#xff0c;改变iter指…...

wordpress创建表格/百度知道入口

前言 数据结构&#xff0c;一门数据处理的艺术&#xff0c;精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美&#xff0c;在为信息技术打下坚实地基的同时&#xff0c;也令无数开发者和探索者为之着迷。 也因如此&#xff0c;它作为博主大二上学期最重要的必…...

word做招聘网站/网站收录优化

&#x1f387;Linux&#xff1a; 博客主页&#xff1a;一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 看似不起波澜的日复一日&#xff0c;一定会在某一天让你看见坚持…...