Using LinearGradientPaint (java 6, Batik) with a BarRenderer
-
- Posts: 29
- Joined: Wed Jun 06, 2007 7:41 pm
Using LinearGradientPaint (java 6, Batik) with a BarRenderer
I am having a drawing issues.
How would I approach filling a single bar (renderer.setSeriesPaint() or renderer.setBasePaint()) with a single LinearGradientPaint?
Every time I try to draw it just applies a single gradient to however many bars i am displaying.
Check this link for an example.
http://www.flickr.com/photos/phirstube/1185042399/
How would I approach filling a single bar (renderer.setSeriesPaint() or renderer.setBasePaint()) with a single LinearGradientPaint?
Every time I try to draw it just applies a single gradient to however many bars i am displaying.
Check this link for an example.
http://www.flickr.com/photos/phirstube/1185042399/
-
- Posts: 29
- Joined: Wed Jun 06, 2007 7:41 pm
well, simple: If JFreeChart exports to PNG correctly, draws to the screen correctly, then we can safely assume that JFreeChart is not the cause of your problems.
JFreeChart makes no use of any special features of Batik. However, JFreeChart assumes that Batik obeys to the Graphics2D contract spelled out by the reference implementation (also known as AWT). So if JFreeChart rendering on the AWT behaves correctly, then the implementor of the third-party Graphics2D context is to blame.
In your case: File a bug report at the Batik-project.
Have fun,
said Thomas
JFreeChart makes no use of any special features of Batik. However, JFreeChart assumes that Batik obeys to the Graphics2D contract spelled out by the reference implementation (also known as AWT). So if JFreeChart rendering on the AWT behaves correctly, then the implementor of the third-party Graphics2D context is to blame.
In your case: File a bug report at the Batik-project.
Have fun,
said Thomas
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Maybe, LinearGradientPaint is a new class in Java 6, and since JFreeChart is supporting Java 1.3 upwards, we don't have any code that specifically recognises this gradient paint. Potentially we could modify the StandardGradientPaintTransformer to use reflection and handle this as a special case...I'll look into it.MattBallard wrote:should i assume that this is a bug with jfreechart?
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


-
- Posts: 29
- Joined: Wed Jun 06, 2007 7:41 pm
I appreciate you looking into this possibility. Please keep me posted.david.gilbert wrote:Maybe, LinearGradientPaint is a new class in Java 6, and since JFreeChart is supporting Java 1.3 upwards, we don't have any code that specifically recognises this gradient paint. Potentially we could modify the StandardGradientPaintTransformer to use reflection and handle this as a special case...I'll look into it.MattBallard wrote:should i assume that this is a bug with jfreechart?
-
- Posts: 29
- Joined: Wed Jun 06, 2007 7:41 pm
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Only some renderers support the transform, for example BarRenderer uses the following code (in the drawItem() method) to detect and transform a GradientPaint:
The existing paint plus a shape ('bar' in the code) is passed to the GradientPaintTransformer, which returns a new GradientPaint instance with coordinates modified in some way to fit the shape that has been passed in.
This same mechanism could be extended to handle other types of gradient paint...but to include it in the standard JFreeChart release, it would have to compile and run using JDK 1.3, so the code would need to use reflection to detect the new LinearGradientPaint class, and fail gracefully on Java 5 and lower.
Code: Select all
Paint itemPaint = getItemPaint(row, column);
GradientPaintTransformer t = getGradientPaintTransformer();
if (t != null && itemPaint instanceof GradientPaint) {
itemPaint = t.transform((GradientPaint) itemPaint, bar);
}
g2.setPaint(itemPaint);
g2.fill(bar);
This same mechanism could be extended to handle other types of gradient paint...but to include it in the standard JFreeChart release, it would have to compile and run using JDK 1.3, so the code would need to use reflection to detect the new LinearGradientPaint class, and fail gracefully on Java 5 and lower.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


-
- Posts: 29
- Joined: Wed Jun 06, 2007 7:41 pm
My specialized DiagPaint extends GradientPaint which implements Paint (see below)
If you throw this inside a BarRenderer using the setPaint function it shows the bars as a gradient that is only two colors.
Inside a Java JPanel it displays the gradient correctly (all three colors)
Corrected Code as follows:
If you throw this inside a BarRenderer using the setPaint function it shows the bars as a gradient that is only two colors.
Inside a Java JPanel it displays the gradient correctly (all three colors)
Corrected Code as follows:
Code: Select all
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
public class Revisited extends JPanel implements ActionListener {
boolean showGradient = true;
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth();
int h = getHeight();
if(showGradient) {
g2.setPaint(new DiagPaint(0, h, Color.red,
w/2, h/2, Color.yellow,
w, 0, Color.green.darker()));
g2.fillRect(0,0,w,h);
} else {
int x1 = 0, y1 = h-1;
int x2 = w/2, y2 = h/2;
int x3 = w-1, y3 = 0;
int[] xpoints = { x1, x1, x2 };
int[] ypoints = { y2, y1, y1 };
g2.draw(new Polygon(xpoints, ypoints, 3)); // sw corner
xpoints = new int[] { x1, x2, x3, x1 };
ypoints = new int[] { y2, y1, y1, y3 };
g2.draw(new Polygon(xpoints, ypoints, 4)); // sw strip
xpoints = new int[] { 0, x3, x3, x2 };
ypoints = new int[] { 0, y1, y2, y3 };
g2.draw(new Polygon(xpoints, ypoints, 4)); // ne strip
xpoints = new int[] { x2, x3, x3 };
ypoints = new int[] { y3, y2, y3 };
g2.draw(new Polygon(xpoints, ypoints, 3)); // ne corner
markColorOrigin(x1, y1, Color.red, g2);
markColorOrigin(x2, y2, Color.yellow, g2);
markColorOrigin(x3, y3, Color.green.darker(), g2);
}
}
private void markColorOrigin(int x, int y, Color color, Graphics2D g2) {
g2.setPaint(color);
g2.fill(new Ellipse2D.Double(x-10, y-10, 20, 20));
}
public void actionPerformed(ActionEvent e) {
String ac = e.getActionCommand();
showGradient = Boolean.parseBoolean(ac);
repaint();
}
private JPanel getLast() {
String[] ids = { "gradient", "strategy" };
ButtonGroup group = new ButtonGroup();
JPanel panel = new JPanel();
for(int j = 0; j < ids.length; j++) {
JRadioButton rb = new JRadioButton(ids[j],j==0);
rb.setActionCommand((j==0) ? "true" : "false");
rb.addActionListener(this);
group.add(rb);
panel.add(rb);
}
return panel;
}
public static void main(String[] args) {
Revisited test = new Revisited();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(test);
f.getContentPane().add(test.getLast(), "Last");
f.setSize(500,400);
f.setLocation(200,200);
f.setVisible(true);
}
}
class DiagContext implements PaintContext {
float x1, y1;
float x2, y2;
float x3, y3;
Color color1;
Color color2;
Color color3;
Polygon swc, sws, nes, nec;
Line2D.Double diagonalLine;
double parallelDist;
Map<Integer,Double> lookup;
public DiagContext(float x1, float y1, Color color1,
float x2, float y2, Color color2,
float x3, float y3, Color color3) {
this.x1 = x1; this.y1 = y1; this.color1 = color1;
this.x2 = x2; this.y2 = y2; this.color2 = color2;
this.x3 = x3; this.y3 = y3; this.color3 = color3;
int[] xpoints = { (int)x1, (int)x1, (int)x2 };
int[] ypoints = { (int)y2, (int)y1, (int)y1 };
swc = new Polygon(xpoints, ypoints, 3); // sw corner
xpoints = new int[] { (int)x1, (int)x2, (int)x3, (int)x1 };
ypoints = new int[] { (int)y2, (int)y1, (int)y1, (int)y3 };
sws = new Polygon(xpoints, ypoints, 4); // sw strip
xpoints = new int[] { 0, (int)x3, (int)x3, (int)x2 };
ypoints = new int[] { 0, (int)y1, (int)y2, (int)y3 };
nes = new Polygon(xpoints, ypoints, 4); // ne strip
xpoints = new int[] { (int)x2, (int)x3, (int)x3 };
ypoints = new int[] { (int)y3, (int)y2, (int)y3 };
nec = new Polygon(xpoints, ypoints, 3); // ne corner
diagonalLine = new Line2D.Double(x1, y3, x3, y1);
// Orthogonal distance between parallel lines.
parallelDist = diagonalLine.ptLineDist(x1, y2);
createLookup();
}
public void dispose() {}
public ColorModel getColorModel() { return ColorModel.getRGBdefault(); }
public Raster getRaster(int x, int y, int w, int h) {
WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h);
int[] data = new int[w * h * 4];
Color start = color1;
Color end = color1;
double distance = 1.0, total = 1.0;
for(int k = 0; k < h; k++) {
for(int j = 0; j < w; j++) {
if(swc.contains(x+j, y+k)) {
double dy = (y+k) - y1;
double dx = (x+j) - x1;
distance = Math.sqrt(dx*dx + dy*dy);
int theta = (int)Math.round(Math.toDegrees(Math.atan2(dy,dx)));
Integer key = Integer.valueOf(theta);
total = ((Double)lookup.get(key)).doubleValue();
start = color1;
end = getBlend(color1, color2);
} else if(sws.contains(x+j, y+k)) {
distance = diagonalLine.ptSegDist((x+j), (y+k));
total = parallelDist;
start = color2;
end = getBlend(color1, color2);
} else if(nes.contains(x+j, y+k)) {
distance = diagonalLine.ptSegDist((x+j), (y+k));
total = parallelDist;
start = color2;
end = getBlend(color2, color3);
} else if(nec.contains(x+j, y+k)) {
double dy = (y+k);
double dx = (x+j) - x3;
distance = Math.sqrt(dx*dx + dy*dy);
int theta =
(int)Math.round(Math.toDegrees(Math.atan2(dy,dx)-Math.PI));
Integer key = Integer.valueOf(theta);
total = ((Double)lookup.get(key)).doubleValue();
start = color3;
end = getBlend(color2, color3);
}
double ratio = distance / total;
if(ratio > 1.0)
ratio = 1.0;
int base = (k * w + j) * 4;
data[base + 0] = (int)(start.getRed() + ratio *
(end.getRed() - start.getRed()));
data[base + 1] = (int)(start.getGreen() + ratio *
(end.getGreen() - start.getGreen()));
data[base + 2] = (int)(start.getBlue() + ratio *
(end.getBlue() - start.getBlue()));
data[base + 3] = (int)(start.getAlpha() + ratio *
(end.getAlpha() - start.getAlpha()));
}
}
raster.setPixels(0, 0, w, h, data);
return raster;
}
private Color getBlend(Color c1, Color c2) {
int r = (c1.getRed() + c2.getRed())/2;
int g = (c1.getGreen() + c2.getGreen())/2;
int b = (c1.getBlue() + c2.getBlue())/2;
return new Color(r, g, b);
}
/** Distance from corner to closest line for [0 - 90] degrees of arc. */
private void createLookup() {
lookup = new HashMap<Integer,Double>();
double length = Math.sqrt(x2*x2 + y2*y2);
Line2D.Double fixed = new Line2D.Double(x1,y2,x2,y1);
// Clockwise from -90 (12 o'clock) by degree -90 -> 0.
for(int j = -90; j <= 0; j++) {
double theta = Math.toRadians(j);
double x = x1 + length*Math.cos(theta);
double y = y1 + length*Math.sin(theta);
Line2D.Double sweep = new Line2D.Double(x1,y1,x,y);
Point2D.Double p = getIntersection(fixed, sweep);
double dx = x1 - p.x;
double dy = y1 - p.y;
double distance = Math.sqrt(dx*dx + dy*dy);
lookup.put(Integer.valueOf(j), Double.valueOf(distance));
}
}
private Point2D.Double getIntersection(Line2D.Double line1, Line2D.Double line2) {
double x1 = line1.getX1();
double y1 = line1.getY1();
double x2 = line1.getX2();
double y2 = line1.getY2();
double x3 = line2.getX1();
double y3 = line2.getY1();
double x4 = line2.getX2();
double y4 = line2.getY2();
double aDividend = (x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3);
double aDivisor = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
double ua = aDividend / aDivisor;
double bDividend = (x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3);
double bDivisor = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
double ub = bDividend / bDivisor;
Point2D.Double p = new Point2D.Double();
p.x = x1 + ua * (x2 - x1);
p.y = y1 + ua * (y2 - y1);
return p;
}
}
class DiagPaint extends GradientPaint {
float x1, y1;
float x2, y2;
float x3, y3;
Color color1;
Color color2;
Color color3;
public DiagPaint(float x1, float y1, Color color1,
float x2, float y2, Color color2,
float x3, float y3, Color color3) {
super(x1,y1,color1,x2,y2,color2);
this.x1 = x1; this.y1 = y1; this.color1 = color1;
this.x2 = x2; this.y2 = y2; this.color2 = color2;
this.x3 = x3; this.y3 = y3; this.color3 = color3;
checkGeometry();
}
public PaintContext createContext(ColorModel cm,
Rectangle deviceBounds,
Rectangle2D userBounds,
AffineTransform xform,
RenderingHints hints) {
return new DiagContext(x1, y1, color1,
x2, y2, color2,
x3, y3, color3);
}
public int getTransparency() {
int a1 = color1.getAlpha();
int a2 = color2.getAlpha();
return (((a1 & a2) == 0xff) ? OPAQUE : TRANSLUCENT);
}
private void checkGeometry() {
if(Math.abs((x3 - x2) - (x2 - x1)) > 1f ||
Math.abs((y3 - y2) - (y2 - y1)) > 1f) {
throw new IllegalArgumentException("First point must be northwest " +
"with second in center and third southeast.");
}
}
}
-
- Posts: 29
- Joined: Wed Jun 06, 2007 7:41 pm
It seems as if your implementation is totally ignoring the bounds and affine-transform it got when DiagPaint.createContext(..) gets called. This is very likely to cause your troubles, as your code will never work for scaled and translated operations.
The (slightly changed) code above exposes the same behaviour as your image of the chart. All I did here, was to change the simple unscaled draw operation into a slightly more complex drawing with some scaling and translation. However, your paint implementation still draws the same gradient as if I would have requested a huge rectangle. I assume the output of this test-programm is not what you expected from your paint 
Have fun,
said Thomas
Code: Select all
public class Revisited extends JPanel implements ActionListener
{
boolean showGradient = true;
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth();
int h = getHeight();
if (showGradient)
{
g2.setPaint(new DiagPaint(0, h, Color.red,
w / 2, h / 2, Color.yellow,
w, 0, Color.green.darker()));
final Rectangle2D rect = new Rectangle2D.Double(0,0,100,100);
g2.fill(rect);
g2.translate(200, 100);
g2.fill(rect);
g2.translate(200, 100);
g2.scale(2, 1.5);
g2.fill(rect);
}
else
{
int x1 = 0, y1 = h - 1;
int x2 = w / 2, y2 = h / 2;
int x3 = w - 1, y3 = 0;
int[] xpoints = {x1, x1, x2};
int[] ypoints = {y2, y1, y1};
g2.draw(new Polygon(xpoints, ypoints, 3)); // sw corner
xpoints = new int[]{x1, x2, x3, x1};
ypoints = new int[]{y2, y1, y1, y3};
g2.draw(new Polygon(xpoints, ypoints, 4)); // sw strip
xpoints = new int[]{0, x3, x3, x2};
ypoints = new int[]{0, y1, y2, y3};
g2.draw(new Polygon(xpoints, ypoints, 4)); // ne strip
xpoints = new int[]{x2, x3, x3};
ypoints = new int[]{y3, y2, y3};
g2.draw(new Polygon(xpoints, ypoints, 3)); // ne corner
markColorOrigin(x1, y1, Color.red, g2);
markColorOrigin(x2, y2, Color.yellow, g2);
markColorOrigin(x3, y3, Color.green.darker(), g2);
}
}
private void markColorOrigin(int x, int y, Color color, Graphics2D g2)
{
g2.setPaint(color);
g2.fill(new Ellipse2D.Double(x - 10, y - 10, 20, 20));
}
public void actionPerformed(ActionEvent e)
{
String ac = e.getActionCommand();
showGradient = Boolean.parseBoolean(ac);
repaint();
}
private JPanel getLast()
{
String[] ids = {"gradient", "strategy"};
ButtonGroup group = new ButtonGroup();
JPanel panel = new JPanel();
for (int j = 0; j < ids.length; j++)
{
JRadioButton rb = new JRadioButton(ids[j], j == 0);
rb.setActionCommand((j == 0) ? "true" : "false");
rb.addActionListener(this);
group.add(rb);
panel.add(rb);
}
return panel;
}
public static void main(String[] args)
{
Revisited test = new Revisited();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(test);
f.getContentPane().add(test.getLast(), "Last");
f.setSize(500, 400);
f.setLocation(200, 200);
f.setVisible(true);
}
}

Have fun,
said Thomas
-
- Posts: 29
- Joined: Wed Jun 06, 2007 7:41 pm
-
- Posts: 29
- Joined: Wed Jun 06, 2007 7:41 pm
This is my new approach:
Since my paintContext does not take into consideration bounds and affine transforms. I have decided to make my createContext call LinearGradientPaints Context. I have also made my TriPaint extend Gradient Paint.
Please note I am using Apache Batik's version of LinearGradientPaint and not Java 6.
inside my constructor it looks like this:
inside my paintContext method i simply call:
Everthing compiles fine, however when my gradient is returned it looks exactly like a GradientPaint. I figured out why. My createContext is NEVER getting called.
I started checking the stack and noticed that BarRenderer.drawItem never calls my paintContext method. Kinda strange behavior. If I was working in C, I would have chalked it up to slicing. Am I overlooking something? Do I need to override the drawItem in order to get it to call my createContext method?
Since my paintContext does not take into consideration bounds and affine transforms. I have decided to make my createContext call LinearGradientPaints Context. I have also made my TriPaint extend Gradient Paint.
Please note I am using Apache Batik's version of LinearGradientPaint and not Java 6.
Code: Select all
public class TriGradientPaint extends GradientPaint
Code: Select all
public TriGradientPaint(Point2D start, Point2D end, float[] fractions, Color[] colors) {
super((float)start.getX(), (float)start.getY(), colors[1], (float)end.getX(), (float)end.getY(), colors[2]);
this.colors = colors;
LinearGradientPaint lgp = new LinearGradientPaint(start,end,fractions,colors);
Code: Select all
public PaintContext createContext(ColorModel cm,
Rectangle deviceBounds,
Rectangle2D userBounds,
AffineTransform xform,
RenderingHints hints) {
return lgp.createContext( cm,
deviceBounds,
userBounds,
xform,
hints);
I started checking the stack and noticed that BarRenderer.drawItem never calls my paintContext method. Kinda strange behavior. If I was working in C, I would have chalked it up to slicing. Am I overlooking something? Do I need to override the drawItem in order to get it to call my createContext method?