Java使用JFreeChart绘制平滑曲线

Java使用JFreeChart绘制平滑曲线_目录

    • 前言
    • 使用依赖
    • 简单Demo
    • 复杂代码
    • 拓展:如何得到你刚画出来的曲线的导数图?

转载请注明出处!

前言

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

使用依赖



	org.jfree
	jfreechart
	1.0.19

简单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);
    }
}

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

复杂代码

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

/**
 * @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 categorie, List> 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);

	}

	/**
	 * 折线图
	 * 

* 创建图表步骤:
* 1:创建数据集合
* 2:创建Chart:
* 3:设置抗锯齿,防止字体显示不清楚
* 4:对柱子进行渲染,
* 5:对其他部分进行渲染
* 6:使用chartPanel接收
* *

*/ //创建折线图图表 private DefaultCategoryDataset createDataset(List categorie, List> series){ // 标注类别 String[] categories = categorie.toArray(new String[0]); // 1:创建数据集合 return CreateChartUtil.createDefaultCategoryDataset(series, categories); } /** * 创建类别数据集合 */ private static DefaultCategoryDataset createDefaultCategoryDataset(List> series, String[] categories){ DefaultCategoryDataset dataset = new DefaultCategoryDataset(); for(Map serieMap : series){ Serie serie = new Serie().fromMap(serieMap); String name = serie.getLabel(); List 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 files, String path){ try { Integer allWidth = 0; //计算画布总宽 Integer allHeight = 0; //计算画布总高 List imgs = new ArrayList<>(); for(int i=0; i 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正弦函数,可能是因为这个。

你可能感兴趣的:(后端,Java,实用工具,java,jreechart,后端)