扩展 jfreechart 的简单方法 (附例子下载)
By:Roy.LiuLast updated:2014-05-19
jFreeChart 是一个利用java 生成图表的工具,可以生成各种类型的图:柱状图,饼图,甘特图,甚至类似于股票里面的走势图等等,功能非常的强大,这是一个免费试用的工具,但作者的网站说:文档是需要收费的。其实作为一般的用法来说,不需要文档就可以完成,网上有太多的例子。我自己也参考了网上的很多例子,但现在项目中的要求是必须与另一个项目生成的 饼图,柱状图基本一致(包括颜色,大小,风格等). 这并需要太多的扩展,基本的样式,直接用 jfreechart 设置 就可以做到, 但有一点在饼图的 label 显示风格不一样,这样就只能扩展了.
先看一下,要求生成的样式如下:

经过查询网上资料,直接用原生的 jfreechart API调整过颜色之后的图如下:

很明显,最重要的一点就是 label 的位置不一样,一个在于线并排,一个在线的下方,而且在线下方的时候,完全靠左或右对齐. 要扩展自己的 饼图类,并达到这样的效果.
扩展的方法,在 jfreechart 中无论是饼图还是柱状图,都有对应的 Plot 类,所要进行的扩展基本都在这里面进行, 就那我自己要扩展的饼图来说, 需要继承 PiePlot 进行扩展. 需要扩展的方法如下图所示:

详细代码如下:
在扩展的代码里,因为 demo 的关系,我 hardcode 了几个数字,第一个是label 的最大宽度,在 drawLeftLabels 这样的方法中,我给了 200 这个固定宽度,原来的代码是通过计算得到的,当然自己也可以通过计算得到,由于我生成的图片大小是固定的,因此我修改成了满足自己的值,第二个 hardcode 的值是 划线的X轴坐标,我用了类似语句: double anchorX = state.getLinkArea().getMaxX() + 128; 后面的 128 是写死的,当然也可以通过计算,通过圆心的坐标,图片宽度,加上state 里面提供的参数,是可以动态计算的。
通过这样的扩展 jfreechart 基本可以满足自己的需要,另外柱状图可以采用类似的方法扩展. 整个项目的 maven 工程下载:
extend jfreechart draw labels
先看一下,要求生成的样式如下:

经过查询网上资料,直接用原生的 jfreechart API调整过颜色之后的图如下:

很明显,最重要的一点就是 label 的位置不一样,一个在于线并排,一个在线的下方,而且在线下方的时候,完全靠左或右对齐. 要扩展自己的 饼图类,并达到这样的效果.
扩展的方法,在 jfreechart 中无论是饼图还是柱状图,都有对应的 Plot 类,所要进行的扩展基本都在这里面进行, 就那我自己要扩展的饼图来说, 需要继承 PiePlot 进行扩展. 需要扩展的方法如下图所示:

详细代码如下:
@Override
protected void drawLeftLabels(KeyedValues leftKeys, Graphics2D g2,
Rectangle2D plotArea, Rectangle2D linkArea, float maxLabelWidth,
PiePlotState state) {
this.getLabelDistributor().clear();
double lGap = plotArea.getWidth() * this.getLabelGap();
double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0;
for (int i = 0; i < leftKeys.getItemCount(); i++) {
String label = this.getLabelGenerator().generateSectionLabel(
this.getDataset(), leftKeys.getKey(i));
if (label != null) {
TextBlock block = TextUtilities.createTextBlock(label,
this.getLabelFont(), this.getLabelPaint(), 200,
new G2TextMeasurer(g2));
TextBox labelBox = new TextBox(block);
labelBox.setBackgroundPaint(this.getLabelBackgroundPaint());
labelBox.setOutlinePaint(this.getLabelOutlinePaint());
labelBox.setOutlineStroke(this.getLabelOutlineStroke());
labelBox.setShadowPaint(this.getLabelShadowPaint());
labelBox.setInteriorGap(this.getLabelPadding());
double theta = Math.toRadians(
leftKeys.getValue(i).doubleValue());
double baseY = state.getPieCenterY() - Math.sin(theta)
* verticalLinkRadius;
double hh = labelBox.getHeight(g2);
this.getLabelDistributor().addPieLabelRecord(new PieLabelRecord(
leftKeys.getKey(i), theta, baseY, labelBox, hh,
lGap / 2.0 + lGap / 2.0 * -Math.cos(theta), 1.0
- getLabelLinkDepth()
+ getExplodePercent(leftKeys.getKey(i))));
}
}
double hh = plotArea.getHeight();
double gap = hh * getInteriorGap();
this.getLabelDistributor().distributeLabels(plotArea.getMinY() + gap,
hh - 2 * gap);
for (int i = 0; i < this.getLabelDistributor().getItemCount(); i++) {
drawLeftLabel(g2, state,
this.getLabelDistributor().getPieLabelRecord(i));
}
}
@Override
protected void drawRightLabels(KeyedValues keys, Graphics2D g2,
Rectangle2D plotArea, Rectangle2D linkArea, float maxLabelWidth,
PiePlotState state) {
// draw the right labels...
this.getLabelDistributor().clear();
double lGap = plotArea.getWidth() * this.getLabelGap();
double verticalLinkRadius = state.getLinkArea().getHeight() / 2.0;
for (int i = 0; i < keys.getItemCount(); i++) {
String label = this.getLabelGenerator().generateSectionLabel(
this.getDataset(), keys.getKey(i));
if (label != null) {
TextBlock block = TextUtilities.createTextBlock(label,
this.getLabelFont(), this.getLabelPaint(), 200,
new G2TextMeasurer(g2));
TextBox labelBox = new TextBox(block);
labelBox.setBackgroundPaint(this.getLabelBackgroundPaint());
labelBox.setOutlinePaint(this.getLabelOutlinePaint());
labelBox.setOutlineStroke(this.getLabelOutlineStroke());
labelBox.setShadowPaint(this.getLabelShadowPaint());
labelBox.setInteriorGap(this.getLabelPadding());
double theta = Math.toRadians(keys.getValue(i).doubleValue());
double baseY = state.getPieCenterY()
- Math.sin(theta) * verticalLinkRadius;
double hh = labelBox.getHeight(g2);
this.getLabelDistributor().addPieLabelRecord(new PieLabelRecord(
keys.getKey(i), theta, baseY, labelBox, hh,
lGap / 2.0 + lGap / 2.0 * Math.cos(theta),
1.0 - getLabelLinkDepth()
+ getExplodePercent(keys.getKey(i))));
}
}
double hh = plotArea.getHeight();
double gap = hh * getInteriorGap();
this.getLabelDistributor().distributeLabels(plotArea.getMinY() + gap,
hh - 2 * gap);
for (int i = 0; i < this.getLabelDistributor().getItemCount(); i++) {
drawRightLabel(g2, state,
this.getLabelDistributor().getPieLabelRecord(i));
}
}
@Override
protected void drawLeftLabel(Graphics2D g2, PiePlotState state,
PieLabelRecord record) {
double anchorX = state.getLinkArea().getMinX() - 129;
double targetX = anchorX;
double targetY = record.getAllocatedY();
if ( getLabelLinksVisible() ) {
double theta = record.getAngle();
double linkX = state.getPieCenterX() + Math.cos(theta)
* state.getPieWRadius() * record.getLinkPercent();
double linkY = state.getPieCenterY() - Math.sin(theta)
* state.getPieHRadius() * record.getLinkPercent();
double elbowX = state.getPieCenterX() + Math.cos(theta)
* state.getLinkArea().getWidth() / 2.0;
double elbowY = state.getPieCenterY() - Math.sin(theta)
* state.getLinkArea().getHeight() / 2.0;
double anchorY = elbowY;
g2.setPaint( getLabelLinkPaint() );
g2.setStroke( getLabelLinkStroke() );
PieLabelLinkStyle style = getLabelLinkStyle();
if (style.equals(PieLabelLinkStyle.STANDARD)) {
g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY));
g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY));
g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY));
}
else if (style.equals(PieLabelLinkStyle.QUAD_CURVE)) {
QuadCurve2D q = new QuadCurve2D.Float();
q.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY);
g2.draw(q);
g2.draw(new Line2D.Double(elbowX, elbowY, linkX, linkY));
}
else if (style.equals(PieLabelLinkStyle.CUBIC_CURVE)) {
CubicCurve2D c = new CubicCurve2D .Float();
c.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY,
linkX, linkY);
g2.draw(c);
}
}
TextBox tb = record.getLabel();
tb.setShadowXOffset(0D);
tb.setShadowXOffset(0D);
tb.setInteriorGap(new RectangleInsets(0D, 2D, 0D, 0D));
tb.draw(g2, (float) targetX - 2 , (float) targetY + 8, RectangleAnchor.LEFT);
}
@Override
protected void drawRightLabel(Graphics2D g2, PiePlotState state,
PieLabelRecord record) {
double anchorX = state.getLinkArea().getMaxX() + 128;
double targetX = anchorX ;
double targetY = record.getAllocatedY();
if (this.getLabelLinksVisible()) {
double theta = record.getAngle();
double linkX = state.getPieCenterX() + Math.cos(theta)
* state.getPieWRadius() * record.getLinkPercent();
double linkY = state.getPieCenterY() - Math.sin(theta)
* state.getPieHRadius() * record.getLinkPercent();
double elbowX = state.getPieCenterX() + Math.cos(theta)
* state.getLinkArea().getWidth() / 2.0;
double elbowY = state.getPieCenterY() - Math.sin(theta)
* state.getLinkArea().getHeight() / 2.0 ;
double anchorY = elbowY;
g2.setPaint(this.getLabelLinkPaint());
g2.setStroke(this.getLabelLinkStroke());
PieLabelLinkStyle style = getLabelLinkStyle();
if (style.equals(PieLabelLinkStyle.STANDARD)) {
g2.draw(new Line2D.Double(linkX, linkY, elbowX, elbowY));
g2.draw(new Line2D.Double(anchorX, anchorY, elbowX, elbowY));
g2.draw(new Line2D.Double(anchorX, anchorY, targetX, targetY));
}
else if (style.equals(PieLabelLinkStyle.QUAD_CURVE)) {
QuadCurve2D q = new QuadCurve2D.Float();
q.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY);
g2.draw(q);
g2.draw(new Line2D.Double(elbowX, elbowY, linkX, linkY));
}
else if (style.equals(PieLabelLinkStyle.CUBIC_CURVE)) {
CubicCurve2D c = new CubicCurve2D .Float();
c.setCurve(targetX, targetY, anchorX, anchorY, elbowX, elbowY,
linkX, linkY);
g2.draw(c);
}
}
TextBox tb = record.getLabel();
tb.setShadowXOffset(0D);
tb.setShadowXOffset(0D);
tb.setInteriorGap(new RectangleInsets(0D, 0D, 0D, 0D));
tb.draw(g2, (float) targetX , (float) targetY + 8, RectangleAnchor.RIGHT);
}
@Override
protected void drawLabels(Graphics2D g2, List keys, double totalValue,
Rectangle2D plotArea, Rectangle2D linkArea, PiePlotState state) {
Composite originalComposite = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1.0f));
// classify the keys according to which side the label will appear...
DefaultKeyedValues leftKeys = new DefaultKeyedValues();
DefaultKeyedValues rightKeys = new DefaultKeyedValues();
double runningTotal = 0.0;
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
Comparable key = (Comparable) iterator.next();
boolean include = true;
double v = 0.0;
Number n = this.getDataset().getValue(key);
if (n == null) {
include = !this.getIgnoreNullValues();
}
else {
v = n.doubleValue();
include = this.getIgnoreZeroValues() ? v > 0.0 : v >= 0.0;
}
if (include) {
runningTotal = runningTotal + v;
// work out the mid angle (0 - 90 and 270 - 360) = right,
// otherwise left
double mid = this.getStartAngle() + (this.getDirection().getFactor()
* ((runningTotal - v / 2.0) * 360) / totalValue);
if (Math.cos(Math.toRadians(mid)) < 0.0) {
leftKeys.addValue(key, new Double(mid));
}
else {
rightKeys.addValue(key, new Double(mid));
}
}
}
g2.setFont(getLabelFont());
// calculate the max label width from the plot dimensions, because
// a circular pie can leave a lot more room for labels...
double marginX= plotArea.getX() + this.getInteriorGap()
* plotArea.getWidth();
double gap = plotArea.getWidth() * this.getLabelGap();
double ww = linkArea.getX() - gap - marginX;
float labelWidth = (float) this.getLabelPadding().trimWidth(ww);
// draw the labels...
if (this.getLabelGenerator() != null) {
drawLeftLabels(leftKeys, g2, plotArea, linkArea, labelWidth,
state);
drawRightLabels(rightKeys, g2, plotArea, linkArea, labelWidth,
state);
}
g2.setComposite(originalComposite);
}
在扩展的代码里,因为 demo 的关系,我 hardcode 了几个数字,第一个是label 的最大宽度,在 drawLeftLabels 这样的方法中,我给了 200 这个固定宽度,原来的代码是通过计算得到的,当然自己也可以通过计算得到,由于我生成的图片大小是固定的,因此我修改成了满足自己的值,第二个 hardcode 的值是 划线的X轴坐标,我用了类似语句: double anchorX = state.getLinkArea().getMaxX() + 128; 后面的 128 是写死的,当然也可以通过计算,通过圆心的坐标,图片宽度,加上state 里面提供的参数,是可以动态计算的。
通过这样的扩展 jfreechart 基本可以满足自己的需要,另外柱状图可以采用类似的方法扩展. 整个项目的 maven 工程下载:
extend jfreechart draw labels
From:一号门
Previous:做日本,香港外包项目的感受

COMMENTS