http://blog.csdn.net/tufeng1992/article/details/50953419
因项目需要画K线图,上面的原文中有些类jFreeChart已经废弃,有些改了类名,运行不起来。花了点时间整理了一下,分享出来给需要的人参考。
SegmentedTimeline类官方已经废弃,可在网上下载之前版本的文件。
Maven依赖:
```
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.3</version>
</dependency>
```
代码:
```
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package com.cq.candlestickchart;
import com.cq.aifocusstocks.dp.common.entity.Bar;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.Shape;
import java.awt.Toolkit;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
import javax.swing.JFrame;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.DateTickUnit;
import org.jfree.chart.axis.DateTickUnitType;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.HighLowItemLabelGenerator;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.CandlestickRenderer;
import org.jfree.chart.renderer.xy.DefaultXYItemRenderer;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.renderer.xy.XYShapeRenderer;
import org.jfree.chart.util.ShapeUtils;
import org.jfree.data.time.Day;
import org.jfree.data.time.Minute;
import org.jfree.data.time.MovingAverage;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.ohlc.OHLCSeries;
import org.jfree.data.time.ohlc.OHLCSeriesCollection;
import org.jfree.data.xy.OHLCDataset;
import org.jfree.data.xy.XYDataset;
/**
*
* @author cqiao
*/
public class CandlesTickChart {
private static Logger logger = LogManager.getLogger(CandlesTickChart.class);
private List<Bar> bars;
private static final int TIME_SCALE = 5;
private static final double UP_OR_DOWN_RANGE = 0.1; //设置price图上下界限比例
private static final int UPPER_RANGE = 10;//设置向上边框距离
private static final long MILLISEC_IN_DAY=24*60*60*1000;
public static void main(String[] args) {
int count = 120;
List<Bar> bars = new ArrayList<>(count);
Random random = new Random();
float currPrice = 8;
Set<LocalDate> focusDates=new HashSet<>();
for (int i = 0; i < count; ++i) {
LocalDateTime tradeDate = LocalDateTime.now().minusDays(count - i);
if(tradeDate.getDayOfWeek().equals(DayOfWeek.SATURDAY)||tradeDate.getDayOfWeek().equals(DayOfWeek.SUNDAY)){
continue;
}
if(i%8==0){
focusDates.add(tradeDate.toLocalDate());
}
Bar bar = new Bar();
bar.setOpenTime(tradeDate);
float open = currPrice * (1 + (random.nextFloat() - 0.5f) / 5);
float max = currPrice * 1.1f;
float min = currPrice * 0.9f;
float high = random.nextFloat() * (max - open) + open;
float low = random.nextFloat() * (open - min) + min;
float close = random.nextFloat() * (high - low) + low;
bar.setOpen(open);
bar.setClose(close);
bar.setLow(low);
bar.setHigh(high);
bar.setVolume((long) (1000 * random.nextFloat()));
bars.add(bar);
currPrice = close;
}
long startTime=System.currentTimeMillis();
CandlesTickChart candlesTickChart = new CandlesTickChart();
candlesTickChart.setBars(bars);
JFrame myFrame = new JFrame();
myFrame.setResizable(true);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JFreeChart chart = candlesTickChart.createCandlesTickChart("贵广网络(600996.SH)",focusDates);
candlesTickChart.saveChartAsPNG("d://贵广网络.png", chart, 600, 450);
myFrame.add(new ChartPanel(chart), BorderLayout.CENTER);
Toolkit kit = Toolkit.getDefaultToolkit();
Insets insets = kit.getScreenInsets(myFrame.getGraphicsConfiguration());
Dimension screen = kit.getScreenSize();
// myFrame.setSize((int) (screen.getWidth() - insets.left - insets.right), (int) (screen.getHeight() - insets.top - insets.bottom));
myFrame.setSize(600, 450);
myFrame.setLocation((int) (insets.left), (int) (insets.top));
myFrame.setVisible(true);
System.out.println("run time="+(System.currentTimeMillis()-startTime));
}
/**
* K线图,带均线和成交量图
*
* @param title K线图的标题
* @param tagDates 在该日期bar下方绘制一个向上的小三角进行标识
* @return
*/
public JFreeChart createCandlesTickChart(String title, Set<LocalDate> tagDates) {
try {
OHLCSeriesCollection seriesCollection = new OHLCSeriesCollection();//定义k线图数据集
OHLCSeries seriesPriceUp = new OHLCSeries("price_up");//高开低收数据序列,股票K线图的四个数据,依次是开,高,低,收
OHLCSeries seriesPriceDown = new OHLCSeries("price_down");//定义上涨和下跌的两个数据集
TimeSeries seriesAllPrice = new TimeSeries("all_price");//对应时间所有价格数据,用于计算均线
TimeSeriesCollection volSeriesCollection = new TimeSeriesCollection();//保留成交量数据的集合
TimeSeries seriesVolumeUp = new TimeSeries("volume_up");//对应时间成交量数据
TimeSeries seriesVolumeDown = new TimeSeries("volume_down");//对应时间成交量数据
TimeSeries seriesAllVolume = new TimeSeries("all_volume");//对应时间所有成交量数据,用于计算均线
TimeSeriesCollection tagSeriesCollection=new TimeSeriesCollection();
TimeSeries seriesTagUp = new TimeSeries("tag_up");//买入标记数据
double mLow = 0d;
double mHigh = 0d;
//统计这段数据包含多少个交易日,好计算设置时间轴刻度规则
int dayCount = bars.size();
Date startDate = Date.from(bars.get(0).getOpenTime().atZone(ZoneId.systemDefault()).toInstant());
Date endDate = Date.from(bars.get(dayCount - 1).getOpenTime().atZone(ZoneId.systemDefault()).toInstant());
//所有非交易日
List<Date> allNonTradedays = new ArrayList<>();
//添加k线图数据,添加成交量数据
int barIndex = 0;
for (LocalDate currDate=bars.get(0).getOpenTime().toLocalDate(); !currDate.isAfter(bars.get(dayCount - 1).getOpenTime().toLocalDate()); currDate=currDate.plusDays(1)) {
Bar bar = bars.get(barIndex);
LocalDateTime openTime = bar.getOpenTime();
if(!openTime.toLocalDate().equals(currDate)){
allNonTradedays.add(Date.from(currDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
continue;
}
++barIndex;
Date barDate = Date.from(openTime.atZone(ZoneId.systemDefault()).toInstant());
double open = bar.getOpen();
double close = bar.getClose();
double high = bar.getHigh();
double low = bar.getLow();
double vol = bar.getVolume();
Calendar quoteCalendar = Calendar.getInstance();
quoteCalendar.setTimeInMillis(barDate.getTime());
//取这段交易日内最高和最低价格
if (mHigh < high) {
mHigh = high;
}
if (mLow > low) {
mLow = low;
} else if (mLow == 0) {
mLow = low;
}
RegularTimePeriod day = new Day(quoteCalendar.get(quoteCalendar.DAY_OF_MONTH), quoteCalendar.get(quoteCalendar.MONTH) + 1, quoteCalendar.get(quoteCalendar.YEAR));
if(tagDates.contains(openTime.toLocalDate())){
RegularTimePeriod tagTime=new Minute(0,12,openTime.getDayOfMonth(),openTime.getMonthValue(),openTime.getYear());
seriesTagUp.add(tagTime, low*0.95f);
}
seriesAllPrice.add(day, close);
seriesAllVolume.add(day, vol);
if (open > close) {
seriesPriceDown.add(day, open, high, low, close);
seriesVolumeDown.add(day, vol);
} else {
seriesPriceUp.add(day, open, high, low, close);
seriesVolumeUp.add(day, vol);
}
}
//k线图数据
seriesCollection.addSeries(seriesPriceUp);
seriesCollection.addSeries(seriesPriceDown);
//成交量数据
volSeriesCollection.addSeries(seriesVolumeUp);
volSeriesCollection.addSeries(seriesVolumeDown);
//标记数据
tagSeriesCollection.addSeries(seriesTagUp);
//获取成收盘价均线图数据
TimeSeriesCollection closeMaSeriesConllection = new TimeSeriesCollection();//保留均线图数据的集合
int[] maCounts = new int[]{5, 10, 20, 30, 60};
for (int i = 0; i < maCounts.length; ++i) {
TimeSeries seriesCloseMA = MovingAverage.createMovingAverage(seriesAllPrice, "-MA" + String.valueOf(maCounts[i]), maCounts[i], 0);//对应时间成交量数据,5天
closeMaSeriesConllection.addSeries(seriesCloseMA);
}
//获取成交量均线图数据
TimeSeriesCollection volMaSeriesConllection = new TimeSeriesCollection();//保留均线图数据的集合
TimeSeries seriesVolMA5 = MovingAverage.createMovingAverage(seriesAllVolume, "-MA5", 5, 0);//对应时间成交量数据,5天
TimeSeries seriesVolMa10 = MovingAverage.createMovingAverage(seriesAllVolume, "-MA10", 10, 0);//对应时间成交量数据,10天
TimeSeries seriesVolMa30 = MovingAverage.createMovingAverage(seriesAllVolume, "-MA30", 30, 0);//对应时间成交量数据,30天
volMaSeriesConllection.addSeries(seriesVolMA5);
volMaSeriesConllection.addSeries(seriesVolMa10);
volMaSeriesConllection.addSeries(seriesVolMa30);
double widthGap=0.5;
//设置K线图的画图器
CandlestickRenderer candlestickRender = new CandlestickRenderer(CandlestickRenderer.WIDTHMETHOD_AVERAGE,
false, new CustomHighLowItemLabelGenerator(new SimpleDateFormat("yyyy-MM-dd"), new DecimalFormat("0.00")));
candlestickRender.setUpPaint(Color.gray);//设置股票上涨的K线内部颜色
candlestickRender.setDownPaint(Color.CYAN);//设置股票下跌的K线内部颜色
candlestickRender.setSeriesPaint(1, Color.CYAN);//设置股票下跌的K线边框颜色
candlestickRender.setSeriesPaint(0, Color.RED);//设置股票上涨的K线边框颜色
candlestickRender.setAutoWidthMethod(CandlestickRenderer.WIDTHMETHOD_AVERAGE);//设置如何对K线图的宽度进行设定
candlestickRender.setAutoWidthGap(widthGap);//设置各个K线图之间的间隔
candlestickRender.setSeriesVisibleInLegend(0, false);//设置不显示legend(数据颜色提示)
candlestickRender.setSeriesVisibleInLegend(1, false);//设置不显示legend(数据颜色提示)
//设置k线图y轴参数
NumberAxis y1Axis = new NumberAxis();//设置Y轴,为数值,后面的设置,参考上面的y轴设置
y1Axis.setAutoRange(false);//设置不采用自动设置数据范围
y1Axis.setUpperMargin(UPPER_RANGE);//设置向上边框距离
y1Axis.setLabelFont(new Font("微软雅黑", Font.BOLD, 12));
y1Axis.setRange(mLow - (mLow * UP_OR_DOWN_RANGE), mHigh + (mLow * UP_OR_DOWN_RANGE));//设置y轴数据范围
// y1Axis.setAutoTickUnitSelection(true);//数据轴的数据标签是否自动确定(默认为true)
//设置k线图x轴,也就是时间轴
DateAxis domainAxis = new DateAxis();
domainAxis.setAutoRange(false);//设置不采用自动设置时间范围
//设置时间范围,注意,最大和最小时间设置时需要+ - 。否则时间刻度无法显示
startDate.setTime(startDate.getTime() - MILLISEC_IN_DAY);
endDate.setTime(endDate.getTime() + MILLISEC_IN_DAY);
domainAxis.setRange(startDate, endDate);
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
domainAxis.setAutoTickUnitSelection(false);//设置不采用自动选择刻度值
domainAxis.setTickMarkPosition(DateTickMarkPosition.START);//设置标记的位置
domainAxis.setStandardTickUnits(DateAxis.createStandardDateTickUnits());// 设置标准的时间刻度单位
domainAxis.setTickUnit(new DateTickUnit(DateTickUnitType.DAY, dayCount / TIME_SCALE));// 设置时间刻度的间隔
domainAxis.setDateFormatOverride(dateFormat);//设置时间格式
//设置时间线显示,排除所有节假日
SegmentedTimeline timeline = SegmentedTimeline.newMondayThroughFridayTimeline();
for (Date holiday : allNonTradedays) {
timeline.addException(holiday);
}
domainAxis.setTimeline(timeline);
//设置均线图画图器
XYLineAndShapeRenderer lineAndShapeRenderer = new XYLineAndShapeRenderer();
lineAndShapeRenderer.setDefaultItemLabelsVisible(true);
lineAndShapeRenderer.setSeriesShapesVisible(0, false);//设置不显示数据点模型
lineAndShapeRenderer.setSeriesShapesVisible(1, false);
lineAndShapeRenderer.setSeriesShapesVisible(2, false);
lineAndShapeRenderer.setSeriesShapesVisible(3, false);//设置不显示数据点模型
lineAndShapeRenderer.setSeriesShapesVisible(4, false);
lineAndShapeRenderer.setSeriesShapesVisible(5, false);
lineAndShapeRenderer.setSeriesPaint(0, Color.WHITE);//设置均线颜色
lineAndShapeRenderer.setSeriesPaint(1, Color.YELLOW);
lineAndShapeRenderer.setSeriesPaint(2, Color.MAGENTA);
lineAndShapeRenderer.setSeriesPaint(3, Color.BLUE);
lineAndShapeRenderer.setSeriesPaint(4, Color.GREEN);
//标记形状
Shape tagShap = ShapeUtils.createUpTriangle(6f);//the size factor (equal to half the height of the tagShap).
XYItemRenderer tagRenderer=new XYShapeRenderer();
tagRenderer.setSeriesShape(0, tagShap);
tagRenderer.setSeriesPaint(0, Color.ORANGE);
//生成画图细节 第一个和最后一个参数这里需要设置为null,否则画板加载不同类型的数据时会有类型错误异常
//可能是因为初始化时,构造器内会把统一数据集合设置为传参的数据集类型,画图器可能也是同样一个道理
XYPlot candlestickSubplot = new XYPlot(null, domainAxis, y1Axis, null);
candlestickSubplot.setBackgroundPaint(Color.GRAY);//设置曲线图背景色
candlestickSubplot.setDomainGridlinesVisible(false);//不显示网格
// plot.setRangeGridlinePaint(Color.RED);//设置间距格线颜色为红色
candlestickSubplot.setRangeGridlinesVisible(false);
//将设置好的数据集合和画图器放入画板
candlestickSubplot.setDataset(0, seriesCollection);
candlestickSubplot.setRenderer(0, candlestickRender);
candlestickSubplot.setDataset(1, closeMaSeriesConllection);
candlestickSubplot.setRenderer(1, lineAndShapeRenderer);
candlestickSubplot.setDataset(2, tagSeriesCollection);
candlestickSubplot.setRenderer(2, tagRenderer);
//设置柱状图参数
XYBarRenderer barRenderer = new XYBarRenderer();
barRenderer.setDrawBarOutline(true);//设置显示边框线
barRenderer.setBarPainter(new StandardXYBarPainter());//取消渐变效果
barRenderer.setMargin(widthGap);//设置柱形图之间的间隔
barRenderer.setSeriesPaint(0, Color.GRAY);//设置柱子内部颜色
barRenderer.setSeriesPaint(1, Color.CYAN);//设置柱子内部颜色
barRenderer.setSeriesOutlinePaint(0, Color.RED);//设置柱子边框颜色
barRenderer.setSeriesOutlinePaint(1, Color.CYAN);//设置柱子边框颜色
barRenderer.setSeriesVisibleInLegend(0, false);//设置不显示legend(数据颜色提示)
barRenderer.setSeriesVisibleInLegend(1, false);//设置不显示legend(数据颜色提示)
barRenderer.setShadowVisible(false);//设置没有阴影
//设置柱状图y轴参数
NumberAxis y2Axis = new NumberAxis();//设置Y轴,为数值,后面的设置,参考上面的y轴设置
y2Axis.setLabelFont(new Font("微软雅黑", Font.BOLD, 12));//设置y轴字体
y2Axis.setAutoRange(true);//设置采用自动设置时间范围
y2Axis.setVisible(false);
//这里不设置x轴,x轴参数依照k线图x轴为模板
XYPlot volumeSubplot = new XYPlot(volSeriesCollection, null, y2Axis, barRenderer);
volumeSubplot.setBackgroundPaint(Color.GRAY);//设置曲线图背景色
volumeSubplot.setDomainGridlinesVisible(false);//不显示网格
// plot2.setRangeGridlinePaint(Color.RED);//设置间距格线颜色为红色
volumeSubplot.setRangeGridlinesVisible(false);
volumeSubplot.setDataset(1, volMaSeriesConllection);
volumeSubplot.setRenderer(1, lineAndShapeRenderer);
//建立一个恰当的联合图形区域对象,以x轴为共享轴
CombinedDomainXYPlot domainXYPlot = new CombinedDomainXYPlot(domainAxis);//
domainXYPlot.add(candlestickSubplot, 3);//添加图形区域对象,后面的数字是计算这个区域对象应该占据多大的区域2/3
domainXYPlot.add(volumeSubplot, 1);//添加图形区域对象,后面的数字是计算这个区域对象应该占据多大的区域1/3
domainXYPlot.setGap(5);//设置两个图形区域对象之间的间隔空间
//生成图
JFreeChart chart = new JFreeChart(title, new Font("微软雅黑", Font.BOLD, 12), domainXYPlot, false);
// JFreeChart chart = new JFreeChart(title, new Font("微软雅黑", Font.BOLD, 12), domainXYPlot, true);
// LegendTitle legend = chart.getLegend(); //设置图例太难看,超过3个会报错,所以去掉
// legend.setPosition(RectangleEdge.TOP);
return chart;
} catch (Exception e) {
logger.error(e);
e.printStackTrace();
}
return null;
}
public void saveChartAsPNG(String filePath, JFreeChart chart, int width, int height) {
File file = new File(filePath);
try {
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream output = new FileOutputStream(file);
ChartUtils.writeChartAsPNG(output, chart, width, height);
output.close();
} catch (IOException ex) {
logger.error(ex);
}
}
public List<Bar> getBars() {
return bars;
}
public void setBars(List<Bar> bars) {
this.bars = bars;
}
}
class CustomHighLowItemLabelGenerator extends HighLowItemLabelGenerator {
/**
* The date formatter.
*/
private DateFormat dateFormatter;
/**
* The number formatter.
*/
private NumberFormat numberFormatter;
/**
* Creates a tool tip generator using the supplied date formatter.
*
* @param dateFormatter the date formatter (<code>null</code> not
* permitted).
* @param numberFormatter the number formatter (<code>null</code> not
* permitted).
*/
public CustomHighLowItemLabelGenerator(DateFormat dateFormatter, NumberFormat numberFormatter) {
if (dateFormatter == null) {
throw new IllegalArgumentException("Null 'dateFormatter' argument.");
}
if (numberFormatter == null) {
throw new IllegalArgumentException("Null 'numberFormatter' argument.");
}
this.dateFormatter = dateFormatter;
this.numberFormatter = numberFormatter;
}
/**
* Generates a tooltip text item for a particular item within a series.
*
* @param dataset the dataset.
* @param series the series (zero-based index).
* @param item the item (zero-based index).
*
* @return The tooltip text.
*/
@Override
public String generateToolTip(XYDataset dataset, int series, int item) {
String result = "";
if (dataset instanceof OHLCDataset) {
OHLCDataset d = (OHLCDataset) dataset;
Number high = d.getHigh(series, item);
Number low = d.getLow(series, item);
Number open = d.getOpen(series, item);
Number close = d.getClose(series, item);
Number x = d.getX(series, item);
// result = d.getSeriesKey(series).toString();
if (x != null) {
Date date = new Date(x.longValue());
result = result + "日期=" + this.dateFormatter.format(date);
if (high != null) {
result = result + ",高=" + this.numberFormatter.format(high.doubleValue());
}
if (low != null) {
result = result + ",低=" + this.numberFormatter.format(low.doubleValue());
}
if (open != null) {
result = result + ",开=" + this.numberFormatter.format(open.doubleValue());
}
if (close != null) {
result = result + ",收=" + this.numberFormatter.format(close.doubleValue());
}
}
}
return result;
}
}
```
运行后生成的图: