在非B/S结构的java程序中,要绘制图形,普遍使用的就是jFreeChart。相对于highChart的纯JS实现(highChart官方例子也很多),绘制jFreeChart可能不是那么容易。通常需要自定义一个map存放key和value,然后通过给ChartFactory
工厂传入Dataset
来创建图表对象,当然最麻烦的就是外观显示了,这部分可以参考org.jfree.chart.plot
里的一众实现。
有个需求是这样:绘制一个曲线图,对于其中的每个点x,限定最大值k,即大于k的时候,按k画图,同时标签显示准确的值x。
原始图形
首先想到的是,直接附加一个label属性,然后把value值按最大值k来取,最后画图就可以了。但是看一下代码,就会发现,这个label是根据dataSet
的属性生成的,函数原型是
DefaultCategoryDataset.addValue(Number value, Comparable rowKey, Comparable columnKey)
就是说,只可以添加value和对应的key,以及这对值属于哪个row。所以想通过遍历DataSet
或者类似方式,都会导致两个对象全部变化,达不到要求。
而且,继续看的话,会发现Plot相关的renderer
(用于外观展示)也没有提供什么有用的API,想要达到这个效果,只能硬编码,改变一下实际显示的图表内容函数了~~
首先,挨个函数看一下,发现工程里的Render(这里使用的LineAndShapeRendere
)长的比较像管这个的,其中还有个drawItem
函数,打上断点看一下,没错就是它了。
新建一个类,继承LineAndShapeRenderer
,这样,就可以在plot里通过setRenderer
来指定我们要用的这个对象。然后,对这个函数稍加改装(这里设计的阈值为0.01)
double value = v.doubleValue() > 0.01 ? 0.01 : v.doubleValue();
运行一下,发现输出了第一个图表,但是有点歪扭
看起来,每个点的绘制,都是需要和前面一个点相关联的,接着看代码,改一下
double previous = previousValue.doubleValue() > 0.01 ? 0.01 : previousValue.doubleValue();
然后调整一下plot里的范围等效地方,最后输出图形
代码:
/** * Rewrite {@code drawItem} method, mainly set value limit to 0.01, * if value > 0.01, draw 0.01, else draw original value * @see LineAndShapeRenderer * */ public class LimitValueRenderer extends LineAndShapeRenderer {
public void drawItem(Graphics2D g2, CategoryItemRendererState state,
Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
int pass) {
......
double value = v.doubleValue() > 0.01 ? 0.01 : v.doubleValue();
double y1 = rangeAxis.valueToJava2D(value, dataArea,
plot.getRangeAxisEdge());
if (pass == 0 && getItemLineVisible(row, column)) {
if (column != 0) {
Number previousValue = dataset.getValue(row, column - 1);
if (previousValue != null) {
// previous data point...
double previous = previousValue.doubleValue() > 0.01 ? 0.01 : previousValue.doubleValue();
double x0;
if (this.getUseSeriesOffset()) {
x0 = domainAxis.getCategorySeriesMiddle(
column - 1, dataset.getColumnCount(),
visibleRow, visibleRowCount,
this.getItemMargin(), dataArea,
plot.getDomainAxisEdge());
}
else {
x0 = domainAxis.getCategoryMiddle(column - 1,
getColumnCount(), dataArea,
plot.getDomainAxisEdge());
}
double y0 = rangeAxis.valueToJava2D(previous, dataArea,
plot.getRangeAxisEdge());
......
}
}
}
......
}
}