在非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()); ...... } } } ...... } }