Java使用JFreeChart绘制平滑曲线

20 篇文章 0 订阅
16 篇文章 0 订阅
本文介绍了如何利用Java的JFreeChart库绘制平滑曲线,通过增加数据点数量或者算法处理实现。此外,探讨了如何通过计算相邻点的斜率来近似得到曲线的导数图,虽然存在误差但能展现导数的大概趋势。文中提供了完整的代码示例,包括设置图表样式、创建折线图和处理数据以达到平滑效果。
摘要由CSDN通过智能技术生成

转载请注明出处!

前言

其实JFreeChart就没有绘制曲线的功能,绘制折线图是可以的,但是我们可以通过数据量增大的方式,让点足够密集变成曲线,如果没有足够的数据量,可以通过后期算法加工的方式,在两个点之间获取很多的点形成曲线,我们这里使用的是ChartFactory.createLineChart方法绘制。

使用依赖

<!-- https://mvnrepository.com/artifact/org.jfree/jfreechart -->
<dependency>
	<groupId>org.jfree</groupId>
	<artifactId>jfreechart</artifactId>
	<version>1.0.19</version>
</dependency>

简单Demo

下面这是一个简单的Demo,代码不复杂,我也没写全备注,大家先复制下来运行成功,看懂代码,然后继续往下看。

package com.xxx.xx.action;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.PlotOrientation;

import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;

import com.topscomm.dist.util.CreateChartUtil;

import java.awt.*;

public class TestWord extends ApplicationFrame {
    public TestWord(String titile){
        //程序入口
        super(titile);
        //获取曲线数据,这里面是我自己造的一条
        CategoryDataset dataset=createDataset();
        //再用数据获取jfreechart对象,这里使用的是createLineChart折线图
        JFreeChart chart=createChart(dataset);
        获取panel,可以用它来设置很多样式的参数,这里就先俺默认了
     	ChartPanel chartPanel=new ChartPanel(chart);
        chartPanel.setPreferredSize(new Dimension(800,500));
        //这个是jfreechart内部的方法
        setContentPane(chartPanel);
    }
    private CategoryDataset createDataset(){
        DefaultCategoryDataset dataset=new DefaultCategoryDataset();
        dataset.addValue(212,"Letter","A");
        dataset.addValue(504,"Letter","B");
        dataset.addValue(1520,"Letter","C");
        dataset.addValue(1842,"Letter","D");
        dataset.addValue(2991,"Letter","E");
        return dataset;
    }
    private JFreeChart createChart(CategoryDataset dataset){
        JFreeChart chart= ChartFactory.createLineChart(
				"Line Chart Demo",
                "Category Axis",
                "Value Axis",
                dataset
        );
        return  chart;
    }
    
    public static void main(String args[])throws Exception{
    	//程序执行从构造方法开始
        TestWord demo=new TestWord("曲线示例");
        //下面这些是弹框展示用的
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);
    }
}

运行后会弹出一个窗口来,这就是我们绘制的曲线图,其严格来说还不能称之为曲线图,但又在某种意义上可以,那么如何将其变成一个曲线图呢?请继续往下看
在这里插入图片描述
想要平滑曲线,就需要足够多的数据,比如说将上图的A-B切分为1000份,这样代码不用改动,只对数据进行处理,就会得到平滑的曲线,如图:
在这里插入图片描述
当然,具体怎么获取很多的数据,完全取决于你的算法,你想让曲线是怎样的一个斜率,都是由你怎么切分数据决定的。

复杂代码

下面再贴一段关于如何设置曲线样式的代码,下面代码无法直接运行,但里面有很详细的注释,设置样式的方式可以参考:

/**
 * @description: 
 * @author: niu
 * @date: 2022年4月7日下午4:34:03
 * @modify: 
*/
package com.xxx.xx.util;

import com.xxx.xx.util.dascore.ChartDto;
import com.xxx.xx.util.dascore.Serie;
import com.xxx.xx.util.dascore.DateStyle;
import com.xxx.xx.util.dascore.DateUtil;
import org.jfree.chart.*;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.DefaultDrawingSupplier;
import org.jfree.chart.plot.PieLabelLinkStyle;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.TextAnchor;
import org.springframework.stereotype.Service;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.text.NumberFormat;
import java.util.*;
import java.util.List;

import javax.imageio.ImageIO;

/**
 * 说明:
 *
 * @description:
 * @program: 
 * @author: nwd
 * @create: 2022-07-18 18:05
 **/
@Service
public class CreateChartUtil{
	private static final String NO_DATA_MSG = "数据加载失败";
	private static final Font FONT = new Font("simsun", Font.PLAIN, 12);
	//颜色
	/*public static Color[] CHART_COLORS = {
			new Color(253,236,109),
			new Color(31,129,188),
			new Color(255,117,153),
			new Color(158,90,102),
			new Color(255,204,102)
	};*/
	//颜色
	public Color[] CHART_COLORS = {
			new Color(255,255,0),
			new Color(0,255,0),
			new Color(255,0,0),
			new Color(0,0,255),
			new Color(103, 78, 167)
	};
	
	/*
	 * 静态代码块
	 */
	static{
		//setChartTheme();
	}
	private void setChartTheme(){
		// 设置中文主题样式 解决乱码
		StandardChartTheme chartTheme = new StandardChartTheme("CN");
		// 设置标题字体
		chartTheme.setExtraLargeFont(FONT);
		// 设置图例的字体
		chartTheme.setRegularFont(FONT);
		// 设置轴向的字体
		chartTheme.setLargeFont(FONT);
		chartTheme.setSmallFont(FONT);
		chartTheme.setTitlePaint(new Color(51, 51, 51));
		chartTheme.setSubtitlePaint(new Color(85, 85, 85));

		chartTheme.setLegendBackgroundPaint(Color.WHITE);
		chartTheme.setLegendItemPaint(Color.BLACK);
		chartTheme.setChartBackgroundPaint(Color.WHITE);
		// 绘制颜色绘制颜色.轮廓供应商
		// paintSequence,outlinePaintSequence,strokeSequence,outlineStrokeSequence,shapeSequence

		Paint[] OUTLINEPAINTSEQUENCE = new Paint[]{Color.WHITE};
		// 绘制器颜色源
		DefaultDrawingSupplier drawingSupplier = new DefaultDrawingSupplier(CHART_COLORS, CHART_COLORS, OUTLINEPAINTSEQUENCE,
				DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE, DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
				DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE);
		chartTheme.setDrawingSupplier(drawingSupplier);

		chartTheme.setPlotBackgroundPaint(Color.BLACK);// 绘制区域
		chartTheme.setPlotOutlinePaint(Color.WHITE);// 绘制区域外边框
		chartTheme.setLabelLinkPaint(new Color(8, 55, 114));// 链接标签颜色
		chartTheme.setLabelLinkStyle(PieLabelLinkStyle.CUBIC_CURVE);

		chartTheme.setAxisOffset(new RectangleInsets(5, 12, 5, 12));
		chartTheme.setDomainGridlinePaint(new Color(192, 208, 224));// X坐标轴垂直网格颜色
		chartTheme.setRangeGridlinePaint(new Color(0, 0, 0));// 用于标识Y轴刻度的横向线条颜色

		chartTheme.setBaselinePaint(Color.WHITE);
		chartTheme.setCrosshairPaint(Color.BLUE);// 不确定含义
		chartTheme.setAxisLabelPaint(new Color(51, 51, 51));// 坐标轴标题文字颜色
		chartTheme.setTickLabelPaint(new Color(67, 67, 72));// 刻度数字
		chartTheme.setBarPainter(new StandardBarPainter());// 设置柱状图渲染
		chartTheme.setXYBarPainter(new StandardXYBarPainter());// XYBar 渲染

		chartTheme.setItemLabelPaint(Color.black);
		chartTheme.setThermometerPaint(Color.white);// 温度计

		ChartFactory.setChartTheme(chartTheme);
	}
	/**
	 * 创建折线图
	 *
	 * @param title     折线图标题
	 * @param xtitle    x轴标题
	 * @param ytitle    y轴标题
	 * @param categorie 横坐标类别
	 * @param series    数据集
	 * @return
	 * @throws Exception
	 */
	private ChartPanel createChart(String title, String xtitle, String ytitle, java.util.List<String> categorie, List<Map<String,Object>> series) throws Exception{

		// 2:创建Chart[创建不同图形]
		JFreeChart chart = ChartFactory.createLineChart(title, xtitle, ytitle, createDataset(categorie, series));
		// 3:设置抗锯齿,防止字体显示不清楚
		CreateChartUtil.setAntiAlias(chart);// 抗锯齿
		// 4:对柱子进行渲染[[采用不同渲染]],暂时注释掉这里,如果加上的话,每个节点就会有符号标识,影响曲线效果
		//CreateChartUtil.setLineRender(chart.getCategoryPlot(), true, true);
		//CategoryPlot plot = chart.getCategoryPlot();
		//LineAndShapeRenderer lasp = (LineAndShapeRenderer) plot.getRenderer();
		// 5:对其他部分进行渲染
		CreateChartUtil.setXAixs(chart.getCategoryPlot());// X坐标轴渲染
		CreateChartUtil.setYAixs(chart.getCategoryPlot());// Y坐标轴渲染
		// 设置标注无边框
		chart.getLegend().setFrame(new BlockBorder(Color.WHITE));
		//设置边框可见
		chart.setBorderVisible(false);
		// 6:使用chartPanel接收
		return new ChartPanel(chart);
	}
	/**
	 * 必须设置文本抗锯齿
	 */
	public static void setAntiAlias(JFreeChart chart){
		chart.setTextAntiAlias(false);

	}
	/**
	 * 设置类别图表(CategoryPlot) X坐标轴线条颜色和样式
	 *
	 */
	public static void setXAixs(CategoryPlot plot){
		Color lineColor = new Color(31, 121, 170);
		plot.getDomainAxis().setAxisLinePaint(lineColor);// X坐标轴颜色
		plot.getDomainAxis().setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色

	}

	/**
	 * 设置类别图表(CategoryPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
	 *
	 */
	public static void setYAixs(CategoryPlot plot){
		Color lineColor = new Color(192, 208, 224);
		ValueAxis axis = plot.getRangeAxis();
		axis.setAxisLinePaint(lineColor);// Y坐标轴颜色
		axis.setTickMarkPaint(lineColor);// Y坐标轴标记|竖线颜色
		// 隐藏Y刻度
		axis.setAxisLineVisible(false);
		axis.setTickMarksVisible(false);
		// 用于标识Y轴刻度的横向线条
		plot.setRangeGridlinePaint(new Color(0, 0, 0));
		plot.setRangeGridlineStroke(new BasicStroke(1));
		// 设置顶部Y坐标轴间距,防止数据无法显示
		plot.getRangeAxis().setUpperMargin(0.1);
		// 设置底部Y坐标轴间距
		plot.getRangeAxis().setLowerMargin(0.1);

	}
	/**
	 * 设置折线图样式
	 *
	 * @param plot
	 * @param isShowDataLabels 是否显示数据标签,如果是折线图,调用此方法,如果绘制曲线图则不要调用
	 */
	@SuppressWarnings("deprecation")
	public
	static void setLineRender(CategoryPlot plot, boolean isShowDataLabels, boolean isShapesVisible){
		plot.setNoDataMessage(NO_DATA_MSG);
		plot.setInsets(new RectangleInsets(10, 5, 0, 5), false);
		LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();

		//renderer.setStroke(new BasicStroke(1.5F));
		if(isShowDataLabels){
			renderer.setBaseItemLabelsVisible(false);
			renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator(StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING,
					NumberFormat.getInstance()));
			renderer.setBasePositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE1, TextAnchor.BOTTOM_CENTER));
		}
		renderer.setBaseShapesVisible(isShapesVisible);
		setXAixs(plot);
		setYAixs(plot);

	}

	/**
	 * 折线图
	 * <p>
	 * 创建图表步骤:<br/>
	 * 1:创建数据集合<br/>
	 * 2:创建Chart:<br/>
	 * 3:设置抗锯齿,防止字体显示不清楚<br/>
	 * 4:对柱子进行渲染,<br/>
	 * 5:对其他部分进行渲染<br/>
	 * 6:使用chartPanel接收<br/>
	 *
	 * </p>
	 */
	//创建折线图图表
	private DefaultCategoryDataset createDataset(List<String> categorie, List<Map<String,Object>> series){
		// 标注类别
		String[] categories = categorie.toArray(new String[0]);
		// 1:创建数据集合
		return CreateChartUtil.createDefaultCategoryDataset(series, categories);
	}
	/**
	 * 创建类别数据集合
	 */
	private static DefaultCategoryDataset createDefaultCategoryDataset(List<Map<String,Object>> series, String[] categories){
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();

		for(Map<String,Object> serieMap : series){
			Serie serie = new Serie().fromMap(serieMap);
			String name = serie.getLabel();
			List<Object> data = serie.getValue();
			if(data != null && categories != null && data.size() == categories.length){
				for(int index = 0; index < data.size(); index++){
					String value = data.get(index) == null ? "" : data.get(index).toString();
					if(isPercent(value)){
						value = value.substring(0, value.length() - 1);
					}
					if(isNumber(value)){
						dataset.setValue(Double.parseDouble(value), name, categories[index]);
					}
				}
			}

		}
		return dataset;

	}
	/**
	 * 是不是一个%形式的百分比
	 *
	 * @param str
	 * @return
	 */
	private static boolean isPercent(String str){
		return str != null && (str.endsWith("%") && isNumber(str.substring(0, str.length() - 1)));
	}

	/**
	 * 是不是一个数字
	 *
	 * @param str
	 * @return
	 */
	private static boolean isNumber(String str){
		return str != null && str.matches("^[-+]?(([0-9]+)((([.]{0})([0-9]*))|(([.]{1})([0-9]+))))$");
	}
	public File drawChart(ChartDto chart,String outputPath, Color[] chartColorParam)throws Exception{
		CHART_COLORS = chartColorParam; 
		setChartTheme();
		ChartPanel chartPanel = createChart(chart.getTitle(), chart.getXAxis(), chart.getYAxis(), chart.getCategories(), chart.getSeries());
		return saveAsFile(chartPanel.getChart(), outputPath);
	}

	/**
	 * 将图表保存为PNG、JPEG图片
	 *
	 * @param chart      折线图对象
	 * @param outputPath 文件保存路径, 包含文件名
	 * @throws Exception
	 */
	private File saveAsFile(JFreeChart chart, String outputPath) throws Exception{
		FileOutputStream out;
		try{
			File outFile = new File(outputPath);
			if(!outFile.getParentFile().exists()){
				outFile.getParentFile().mkdirs();
			}
			out = new FileOutputStream(outputPath);
			// 保存为PNG
			ChartUtilities.writeChartAsPNG(out, chart, 1800, 500);
			out.flush();
			out.close();
			return outFile;
		} catch(IOException e){
			// do nothing
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 将几张图片竖向合成一个
	 * @param folderPath
	 * @Author:         niuwenda
	 */
    public static boolean mergeVertical(List<File> files, String path){
        try {
            Integer allWidth = 0;	//计算画布总宽
            Integer allHeight = 0;	//计算画布总高
            List<BufferedImage> imgs = new ArrayList<>();
            for(int i=0; i<files.size(); i++){
                imgs.add(ImageIO.read(files.get(i)));
                //因为是竖向合成,拿图片里最大的一个宽度就行
                allWidth = Math.max(allWidth,imgs.get(i).getWidth());
                allHeight += imgs.get(i).getHeight();
            }
            BufferedImage combined = new BufferedImage(allWidth, allHeight,BufferedImage.TYPE_INT_RGB);
            Graphics g = combined.getGraphics();
            //设置画布背景颜色 ,默认黑色
            g.setColor(Color.white);
            g.fillRect(0, 0, allWidth, allHeight);
            Integer height = 0;
            for(int i=0; i< imgs.size(); i++){
                g.drawImage(imgs.get(i), 0, height,null);
                //+10为了设置上下两个图片间距
                height +=  imgs.get(i).getHeight();
            }
            //关流防止内存溢出
            g.dispose();
            ImageIO.write(combined, "png", new File(path));
            System.out.println("===图片竖向合成成功====");
            return true;
        } catch (Exception e) {
            System.out.println("===图片竖向合成失败====");
            e.printStackTrace();
            return false;
        }
    }
	//字符串类型图片转换
    public static BufferedImage base64StringToImg(String base64String) {
        try {
          byte[] bytes = Base64.getDecoder().decode(base64String);
         ByteArrayInputStream in = new ByteArrayInputStream(bytes);
         return ImageIO.read(in);
        } catch (final IOException ioe) {
            throw new UncheckedIOException(ioe);
        }
    }
	
	public static void main(String[] args){
		try{
			ChartDto chartDto = new ChartDto().fromJsonStr("{\"DATE\":\"2020-08-12 19:26:23\",\"SERIES\":[{\"LABEL\":\"A\",\"VALUE\":[\"391.03522\",\"385.38327\",\"386.1623\",\"388.39557\",\"389.19617\",\"386.86725\",\"383.91547\",\"384.25452\",\"379.57465\",\"381.97986\",\"383.04315\",\"383.62997\",\"386.57635\",\"383.42267\",\"382.4391\",\"382.73096\",\"382.76874\",\"384.54282\",\"385.13815\",\"386.9652\",\"385.81213\",\"386.14087\",\"380.2163\",\"380.27798\",\"380.99316\",\"381.51575\",\"381.52173\",\"131.59659\",\"0.6947088\",\"0.6007847\"]},{\"LABEL\":\"B\",\"VALUE\":[\"278.26807\",\"277.34164\",\"282.38678\",\"278.49786\",\"277.28955\",\"277.8999\",\"278.23306\",\"276.3892\",\"277.19434\",\"276.4134\",\"274.7296\",\"276.294\",\"273.78204\",\"275.7772\",\"278.87714\",\"277.34637\",\"274.43817\",\"278.10522\",\"276.18378\",\"277.3299\",\"274.9383\",\"275.388\",\"277.2262\",\"276.70746\",\"273.91785\",\"273.8313\",\"274.09454\",\"82.66885\",\"0.5141127\",\"0.50791365\"]},{\"LABEL\":\"C\",\"VALUE\":[\"227.23041\",\"229.29349\",\"227.69107\",\"227.8791\",\"228.49219\",\"229.2131\",\"227.93292\",\"227.7579\",\"228.23328\",\"228.41928\",\"228.08829\",\"228.5531\",\"226.71812\",\"228.02512\",\"227.48405\",\"226.31848\",\"228.68155\",\"228.44812\",\"227.94458\",\"228.10205\",\"227.5925\",\"227.20189\",\"227.05502\",\"226.98502\",\"225.91888\",\"226.94777\",\"226.73824\",\"82.26399\",\"0.60032165\",\"0.5139754\"]}],\"TITLE\":\"9992 electric intensity\",\"X_AXIS\":\"\",\"CATEGORIES\":[\"19:11\",\"19:12\",\"19:13\",\"19:14\",\"19:15\",\"19:16\",\"19:17\",\"19:18\",\"19:19\",\"19:20\",\"19:21\",\"19:22\",\"19:23\",\"19:24\",\"19:25\",\"19:26\",\"19:27\",\"19:28\",\"19:29\",\"19:30\",\"19:31\",\"19:32\",\"19:33\",\"19:34\",\"19:35\",\"19:36\",\"19:37\",\"19:38\",\"19:39\",\"19:40\"],\"Y_AXIS\":\"Electric Intensity\",\"ADDR\":\"9992\"}");
			CreateChartUtil service = new CreateChartUtil();
			Collections.replaceAll(chartDto.getCategories(), DateUtil.dateToString(chartDto.getDate(), DateStyle.HH_MM), "★");
			Collections.replaceAll(chartDto.getCategories(), "19:37", "☆");
			service.drawChart(chartDto,
					"d:\\111.png");
			/*List<File> files = new ArrayList();
			File x = new File("D:\\temp\\testrename\\U.png");
			File y = new File("D:\\temp\\testrename\\I.png");
			files.add(x);
			files.add(y);
			String path = "D:\\temp\\testrename\\3.png";
			mergeVertical(files,path);*/
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

拓展:如何得到你刚画出来的曲线的导数图?

在数学上,求一个点的导数是有公式的,假如曲线上有两个横坐标无限接近的点(n,w)和(n+x,w+y)
那么(n,w)点的导数就是(w+y-w)/(n+x-n)=y/w,同样的,我们上面已经得到了大量的点坐标,并且已经形成曲线,这就说明我们的点是离的够近的,我们可以用这种算法,将数据都循环计算一遍,从而得到一个曲线的导数的曲线。

注意:这样算是有误差的,但是曲线的形状基本上是对的,如果你追求非常精确的话,请再研究一下别的方法。


存疑:很奇怪的是,数学上是按照我刚才说的那么算,但是公司的算法部有个大佬告诉我,直接用w+y-w就是导数,而且我问他为什么这么做,他也不告诉我,如果有朋友知道的话麻烦告诉我一下,我的曲线是个sin正弦函数,可能是因为这个。

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值