Textures.

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
jleech

Textures.

Post by jleech » Tue Aug 28, 2007 11:57 pm

I did a search and it seems it seems there are two problems with textures.
1) Textures using java.awt.TexturePaint don't look good in general, especially when printed.
2) Textures break the SVG output.

I haven't verified either of these problems... But I need textures to print in black and white, and if they don't work with SVG its a show-stopper, and if they simply don't look good, that's bad.

Now that I'm done complaining, the rest of my first post on these forums is going to be constructive.

Here's what I'm going to try. Its sort of a green-screen approach to get nice patterns:
-Instead of TexturePaint, use distinct colors (i.e. not appearing anywhere else in the chart).
-Render the chart to SVG document using batik.
-Add appropriate SVG pattern tags to the defs section of the SVG.
-Find and replace the fill patterns of the distinct colors with the corresponding SVG patterns.
- Use FOP to put the SVG charts in a .pdf, or Batik to render to .png, .jpeg, etc.

Feedback is appreciated, and if / when I come up with something useful I will post a code sample here.

Taqua
JFreeReport Project Leader
Posts: 698
Joined: Fri Mar 14, 2003 3:34 pm
Contact:

Post by Taqua » Wed Aug 29, 2007 7:29 am

Hi,
1) Textures using java.awt.TexturePaint don't look good in general, especially when printed.
Yes, thats a side effect of using raster images. There's not much you can do, except for using a higher resolution texture. But Java's TexturePaint implementation would not allow you to do this, so you have to implement your own.
2) Textures break the SVG output.
Let me rephrase that: Batik is buggy. Batik is buggy. Batik is buggy. Batik cannot handle TexturePaints correctly. Complain at the Batik Project and hope they fix it within the next years.

Now to the constructive part :)

Well, if all what you want is PDF and PNG/JPEG output, then using the buggy Batik system is a masochistic approach.

You can generate good looking PDF output by drawing the chart directly to iText's PDFGraphics2D context. This results in a Vector-Image version of your chart which looks great when being printed.

Rendering to PNG and JPEG is directly supported in JFreeChart. The ChartUtilities class contains the necessary code.

So if you drop Batik, then you not only can remove a couple of megabytes of unnecessary jars from your project, you can also skip a lot of coding and a lot of unnecessary and slow XML processing.

Have fun,
said Thomas

jleech

Post by jleech » Thu Aug 30, 2007 5:30 pm

Update:

I made simple "spot" and "checkboard" patterns, and output charts using the patterns in .png and .svg. Batik handled the TexturePaints I made just fine. What Batik bugs should I be worried about for producing charts?

Mozilla's built in SVG viewer doesn't handle the patterns, neither does Opera's. FOP's SVG rendering does.

The print output quality is OK, but the textures are a little fuzzy. I don't see how to make higher resolution versions and have Batik use a higher DPI.

I realize I can output straight to .pdf using IText, but my charts are parts of much larger reports. I am replacing the commercial big.faceless.org Report Generator(which is a great product btw) with open source equivalents and am looking at things like FlyingSaucer and CSSToXSLFO, as the parts of the reports that arent't charts are essentially XHTML.

My next step is to substitute the raster patterns with SVG ones, and to build more patterns. I am thinking along the lines of a TexturePaint sub-class which starts with an SVG pattern, that Batik can know about and simply use the SVG pattern itself to fill, rather than the raster version.

jleech

Post by jleech » Mon Sep 10, 2007 5:30 pm

Here is code to output high quality textures in SVG via Batik. The source of the textures are SVG pattern elements, and when not rendering to SVG, the patterns are rasterized using Batik.
So far I've tested with Batik 1.6 (which seems to have a bug rendering some Pie charts that 1.5 didn't). I will test with 1.5, and 1.7 once I get 1.7 working under by JBoss instance.
I hope others find this useful.

Code: Select all

package com.virtela.filter;

import java.awt.TexturePaint;
import java.awt.image.BufferedImage;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.Color;
import java.awt.Paint;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.DOMImplementation;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.DefaultExtensionHandler;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.svggen.SVGPaintDescriptor;
import org.apache.batik.svggen.SVGSyntax;

/**
 * A TexturePaint that is created from an SVG Pattern element.
 * The original SVG Pattern element is retained so that a renderer that knows about this class can use it.
 * For example, a Batik extension can ignore the raster BufferedImage and use the original vector pattern.
 * See the getSVGGraphics2D() and getBatikExtensionHandler() static factory methods.
 * These allows high-quality, vector patterns when outputing via Batik to SVG output.
 * SVGPatternTexturePaint also contains static factory methods to create several 2-color patterns,
 * as well as general-purpose constructors to create a pattern from a DOM Element (pattern) or String.
 */
public class SVGPatternTexturePaint 
   extends TexturePaint
{  
   /**
    * Create an SVGPatternTexturePaint that is a repeating brick pattern.
    * @param size The size in pixels of the height and width of the repeating pattern.
    * @param backgroundColor The color of the background (brick).
    * @param strokeColor The color of the stroke (mortar).
    * @param strokeWidth The width of the stroke (mortar), in pixels.
    */
   public static SVGPatternTexturePaint createBrickPaint(int size, Color backgroundColor, Color strokeColor, int strokeWidth) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append("<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />"); 
      sb.append("<path d=\"M 50,100 L 50,50 M 100,0 L 0,0 0,50 100,50\" ");            
      sb.append("style=\"fill:none;stroke:");
      sb.append(colorToCSSHex(strokeColor));
      sb.append(";stroke-width:");
      sb.append(strokeWidth);
      sb.append("\" />");      
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating checkerboard pattern.
    * @param size The size in pixels of the height and width of the repeating pattern.
    * @param backgroundColor The color of the background.
    * @param checkColor The color of the check.    
    */
   public static SVGPatternTexturePaint createCheckPaint(int size, Color backgroundColor, Color checkColor) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append("<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<rect width=\"50\" height=\"50\" x=\"0\" y=\"0\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(checkColor));
      sb.append("\" />");
      sb.append("<rect width=\"50\" height=\"50\" x=\"50\" y=\"50\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(checkColor));
      sb.append("\" />");
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating grid pattern.
    * @param size The size in pixels of the height and width of the repeating pattern.
    * @param backgroundColor The color of the background.
    * @param strokeColor The color of the stroke.
    * @param strokeWidth The width of the stroke, in pixels.
    */
   public static SVGPatternTexturePaint createGridPaint(int size, Color backgroundColor, Color strokeColor, int strokeWidth) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append("<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<path d=\"M 1,99 L 99,99 99,1\" ");
      sb.append("style=\"fill:none;stroke:");
      sb.append(colorToCSSHex(strokeColor));
      sb.append(";stroke-width:");
      sb.append(strokeWidth);
      sb.append("\" />");      
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating polka pattern (random sized and placed spots).
    * @param size The size in pixels of the height and width of the repeating pattern.
    * @param backgroundColor The color of the background.
    * @param polkaColor The color of the polkas.    
    */
   public static SVGPatternTexturePaint createPolkaPaint(int size, Color backgroundColor, Color polkaColor) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append("<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<path d=\"M 30 15 A 15 15 0 1 1  0,15 A 15 15 0 1 1  30 15 z\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");
      sb.append("<path d=\"M 30 15 A 15 15 0 1 1  0,15 A 15 15 0 1 1  30 15 z\" transform=\"matrix(0.866667,0,0,0.866667,21.99999,41)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");     
      sb.append("<path d=\"M 53 86 A 10 10 0 1 1  33,86 A 10 10 0 1 1  53 86 z\" transform=\"matrix(1.05,0,0,1.05,-28.15,-11.3)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");
      sb.append("<path d=\"M 53 86 A 10 10 0 1 1  33,86 A 10 10 0 1 1  53 86 z\" transform=\"matrix(0.9,0,0,0.9,20.3,-56.4)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");
      sb.append("<path d=\"M 53 86 A 10 10 0 1 1  33,86 A 10 10 0 1 1  53 86 z\" transform=\"matrix(0.9,0,0,0.9,36.3,-21.4)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");
      sb.append("<path d=\"M 53 86 A 10 10 0 1 1  33,86 A 10 10 0 1 1  53 86 z\" transform=\"matrix(0.4,0,0,0.4,62.8,53.6)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(polkaColor));
      sb.append("\" />");       
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating spot pattern.
    * @param size The size in pixels of the height and width of the repeating pattern.
    * @param backgroundColor The color of the background.
    * @param spotColor The color of the spots.    
    */
   public static SVGPatternTexturePaint createSpotPaint(int size, Color backgroundColor, Color spotColor) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append("<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />"); 
      sb.append("<circle cx=\"25\" cy=\"25\" r=\"25\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(spotColor));
      sb.append("\" />");
      sb.append("<circle cx=\"75\" cy=\"75\" r=\"25\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(spotColor));
      sb.append("\" />");
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating star pattern.
    * @param size The size in pixels of the height and width of the repeating pattern.
    * @param backgroundColor The color of the background.
    * @param starColor The color of the stars.    
    */
   public static SVGPatternTexturePaint createStarPaint(int size, Color backgroundColor, Color starColor) 
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append("<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<path d=\"M 33.171361,22.362954 C 33.206821,22.469591 28.234412,25.908525 28.199472,26.015333 C 28.164258,26.122981 30.12424,31.889333 30.033069,31.956534 C 29.94261,32.023211 25.13543,28.356858 25.023052,28.356634 C 24.909792,28.356408 20.031332,32.002363 19.939247,31.936421 C 19.84788,31.870993 21.849288,26.166128 21.814775,26.059181 C 21.77999,25.951394 16.804954,22.438365 16.839213,22.33041 C 16.873205,22.223296 22.917323,22.363848 23.00837,22.297976 C 23.100133,22.231585 24.903851,16.41446 25.017109,16.413682 C 25.129485,16.41291 26.863547,22.204641 26.95433,22.270876 C 27.045827,22.337632 33.135622,22.255479 33.171361,22.362954 z\" ");
      sb.append("transform=\"matrix(3.055837,0,0,3.204708,-51.46136,-52.55065)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(starColor));
      sb.append("\" />");
      sb.append("<path d=\"M 33.171361,22.362954 C 33.206821,22.469591 28.234412,25.908525 28.199472,26.015333 C 28.164258,26.122981 30.12424,31.889333 30.033069,31.956534 C 29.94261,32.023211 25.13543,28.356858 25.023052,28.356634 C 24.909792,28.356408 20.031332,32.002363 19.939247,31.936421 C 19.84788,31.870993 21.849288,26.166128 21.814775,26.059181 C 21.77999,25.951394 16.804954,22.438365 16.839213,22.33041 C 16.873205,22.223296 22.917323,22.363848 23.00837,22.297976 C 23.100133,22.231585 24.903851,16.41446 25.017109,16.413682 C 25.129485,16.41291 26.863547,22.204641 26.95433,22.270876 C 27.045827,22.337632 33.135622,22.255479 33.171361,22.362954 z\" ");
      sb.append("transform=\"matrix(3.055837,0,0,3.204708,-1.457891,-2.601058)\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(starColor));
      sb.append("\" />");
      sb.append("</pattern>");
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating stripe pattern (45 degrees).
    * @param size The size in pixels of the height and width of the repeating pattern.
    * @param backgroundColor The color of the background.
    * @param stripeColor The color of the stripes.    
    */
   public static SVGPatternTexturePaint createStripePaint(int size, Color backgroundColor, Color stripeColor)
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append("<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<path d=\"M -2,102 L 102,-2\" ");
      sb.append("style=\"stroke-width:35;stroke:");
      sb.append(colorToCSSHex(stripeColor));
      sb.append("\" />");
      sb.append("<path d=\"M -14,14 L 14,-14\" ");
      sb.append("style=\"stroke-width:35;stroke:");
      sb.append(colorToCSSHex(stripeColor));
      sb.append("\" />");   
      sb.append("<path d=\"M 84,116 L 116,84\" ");
      sb.append("style=\"stroke-width:35;stroke:");
      sb.append(colorToCSSHex(stripeColor));
      sb.append("\" />");
      sb.append("</pattern>");    
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create an SVGPatternTexturePaint that is a repeating pattern of the Virtela whirlygig.
    * The Virtela whirlygig is a Service Mark(SM) of Virtela Communications.
    * @param size The size in pixels of the height and width of the repeating pattern.
    * @param backgroundColor The color of the background.
    * @param virtelaColor The color of the whirlygigs.    
    */
   public static SVGPatternTexturePaint createVirtelaPaint(int size, Color backgroundColor, Color virtelaColor)
      throws SAXException, IOException, TranscoderException
   {
      StringBuilder sb = new StringBuilder();
      sb.append("<pattern patternUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"");
      sb.append(size);
      sb.append("\" height=\"");
      sb.append(size);
      sb.append("\" viewBox=\"0 0 100 100\">");
      sb.append("<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(backgroundColor));
      sb.append("\" />");
      sb.append("<g ");
      sb.append("style=\"stroke:none;fill:");
      sb.append(colorToCSSHex(virtelaColor));
      sb.append("\">");
      sb.append("<g transform=\"matrix(0.980915,0,0,1,-4.530406e-2,-0.126984)\" >");      
      sb.append("<path d=\"M 50.726,44.209 C 50.919,42.959 51.019,41.68 51.019,40.375 C 51.019,26.555 39.845,15.346 26.04,15.286 L 26.04,20.303 C 37.075,20.363 46.001,29.326 46.001,40.375 C 46.001,41.687 45.873,42.968 45.633,44.209 L 50.726,44.209 z\" />");
      sb.append("<path d=\"M 0,44.585 C 0.987,45.378 2.047,46.104 3.177,46.757 C 15.144,53.665 30.438,49.594 37.392,37.668 L 33.045,35.158 C 27.478,44.685 15.252,47.933 5.687,42.41 C 4.547,41.754 3.501,41.003 2.547,40.174 L 0,44.585 z\" />");
      sb.append("<path d=\"M 25.174,0 C 23.994,0.459 22.836,1.014 21.706,1.665 C 9.739,8.575 5.617,23.855 12.469,35.84 L 16.814,33.331 C 11.349,23.746 14.647,11.534 24.216,6.01 C 25.352,5.356 26.526,4.824 27.719,4.411 L 25.174,0 z\" />");
      sb.append("</g>");
      sb.append("<g transform=\"matrix(0.980915,0,0,1,49.9547,50)\" >");
      sb.append("<path d=\"M 50.726,44.209 C 50.919,42.959 51.019,41.68 51.019,40.375 C 51.019,26.555 39.845,15.346 26.04,15.286 L 26.04,20.303 C 37.075,20.363 46.001,29.326 46.001,40.375 C 46.001,41.687 45.873,42.968 45.633,44.209 L 50.726,44.209 z\" />");
      sb.append("<path d=\"M 0,44.585 C 0.987,45.378 2.047,46.104 3.177,46.757 C 15.144,53.665 30.438,49.594 37.392,37.668 L 33.045,35.158 C 27.478,44.685 15.252,47.933 5.687,42.41 C 4.547,41.754 3.501,41.003 2.547,40.174 L 0,44.585 z\" />");
      sb.append("<path d=\"M 25.174,0 C 23.994,0.459 22.836,1.014 21.706,1.665 C 9.739,8.575 5.617,23.855 12.469,35.84 L 16.814,33.331 C 11.349,23.746 14.647,11.534 24.216,6.01 C 25.352,5.356 26.526,4.824 27.719,4.411 L 25.174,0 z\" />");
      sb.append("</g>");
      sb.append("</g>");
      sb.append("</pattern>");    
      return new SVGPatternTexturePaint(sb.toString());
   }
   
   /**
    * Create a Batik SVGraphics2D that is aware of SVGPatternTexturePaint.
    * Overrides Batik default behavior and replaces the texture with the SVGPatternTexturePaint pattern.
    * @return SVGGraphics2D The SVGPatternTexturePaint-aware SVGGraphics2D.
    */
   public static SVGGraphics2D getSVGGraphics2D() {
      DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();                             
      String svgNS = "http://www.w3.org/2000/svg";
      Document document = domImpl.createDocument(svgNS, "svg", null);
      SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document);
      ctx.setExtensionHandler(getBatikExtensionHandler());
      return new SVGGraphics2D(ctx, false); 
   }
   
   /**
    * Create a Batik ExtensionHandler that is aware of SVGPatternTexturePaint.
    * Overrides Batik default behavior and replaces the texture with the SVGPatternTexturePaint pattern.
    * @return DefaultExtensionHandler The SVGPatternTexturePaint-aware extension handler.
    */
   public static DefaultExtensionHandler getBatikExtensionHandler() {
      return new DefaultExtensionHandler() {
         public SVGPaintDescriptor handlePaint(Paint paint, SVGGeneratorContext generatorCtx) {
            if (paint instanceof SVGPatternTexturePaint) {
               SVGPatternTexturePaint pattern = (SVGPatternTexturePaint)paint;
               String id = generatorCtx.getIDGenerator().generateID("pattern");
               Document doc = generatorCtx.getDOMFactory();
               Element pat = (Element)doc.importNode(pattern.getPattern(), true);
               pat.setAttributeNS(null, SVGSyntax.SVG_ID_ATTRIBUTE, id);
               return new SVGPaintDescriptor("url(#" + id + ")", SVGSyntax.SVG_OPAQUE_VALUE, pat);
            }
            return null;
         }
      };
   }
     
   /**
    * Create an SVGPatternTexturePaint from a pattern represented as a String.
    * @param pattern The pattern.
    */
   public SVGPatternTexturePaint(String pattern) 
      throws SAXException, IOException, TranscoderException
   {
      this(parse(pattern));
   }
   
   /**
    * Create an SVGPatternTexturePaint from a pattern represented as a DOM Element.
    * @param pattern The pattern.
    */
   public SVGPatternTexturePaint(Element pattern) 
      throws TranscoderException
   {            
      super(render(pattern), getAnchor(pattern));
      this.pattern = pattern;
   }
   
   /**
    * The pattern represented as a DOM Element.
    */
   Element pattern;
   
   /**
    * Return the pattern.
    */
   public Element getPattern() {
      return pattern;
   }  
   
   /**
    * Parse a pattern represented as a String into a DOM Element
    * @param pattern The pattern.
    * @return The pattern as a DOM Element.
    */
   static Element parse(String pattern) 
      throws SAXException, IOException
   {
      InputSource is = new InputSource(new ByteArrayInputStream(pattern.getBytes()));
      Document doc = documentBuilder.get().parse(is);
      return doc.getDocumentElement();
   }
   
   /**
    * Render the given pattern onto a BufferedImage.
    * @param pattern The pattern.
    * @return A BufferedImage containing the rasterized pattern.
    */
   static BufferedImage render(Element pattern) 
      throws TranscoderException
   {
      // render the SVG to a BufferedImage and return it.
      final BufferedImage[] holder = new BufferedImage[] {null}; // Holds the BufferedImage
      
      // Override Batik ImageTranscoder, don't perform the writeImage step.
      ImageTranscoder transcoder = new ImageTranscoder() {
         public BufferedImage createImage(int width, int height) {
            holder[0] = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            return holder[0];
         }
         public void writeImage(BufferedImage img, TranscoderOutput output) throws TranscoderException {
            // do nothing. The BufferedImage is the final output.
         }
      };
      
      // Use the DOM to build an SVG filling a rect with the pattern.
      DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();                             
      String svgNS = "http://www.w3.org/2000/svg";
      Document doc = domImpl.createDocument(svgNS, "svg", null);     
      Element svg = doc.getDocumentElement();      
      // Set the width and height of the SVG to the width and height of the pattern.
      svg.setAttribute("width", pattern.getAttribute("width"));
      svg.setAttribute("height", pattern.getAttribute("height"));
      Element defs = doc.createElement("defs");      
      Element pattern2 = (Element)doc.importNode(pattern, true);
      pattern2.setAttribute("id", "pattern");
      defs.appendChild(pattern2);
      svg.appendChild(defs);
      Element rect = doc.createElement("rect");
      rect.setAttribute("x", "0");
      rect.setAttribute("y", "0");
      rect.setAttribute("width", pattern.getAttribute("width"));
      rect.setAttribute("height", pattern.getAttribute("height"));
      rect.setAttribute("fill", "url(#pattern)");
      svg.appendChild(rect);
            
      try {
         // Batik hates TranscoderInput(doc) but likes it if I put it out to a string and let Batik read it back in...
         Transformer identity = tFactory.newTransformer();
         StreamResult result = new StreamResult(new StringWriter());
         DOMSource source = new DOMSource(doc);
         identity.transform(source, result);
         String xmlString = result.getWriter().toString();
         ByteArrayInputStream bais = new ByteArrayInputStream(xmlString.getBytes());
         
         // Batik transcode step.
         transcoder.transcode(new TranscoderInput(bais), new TranscoderOutput());
      }            
      catch (TransformerException te) {
         throw new RuntimeException(te);
      }      
      
      return holder[0];
   }
   
   /**
    * Translate an awt Color to a css-friendly hex String.
    * For example Color.white would be "#FFFFFF".
    * null is handled specially as "none".
    * @param color The color to translate.
    * @return The color as a css-friendly String.
    */
   static String colorToCSSHex(Color color) {
      StringBuilder sb = new StringBuilder();
      if (color != null) {
         sb.append("#");
         sb.append(Integer.toHexString(color.getRed()));
         sb.append(Integer.toHexString(color.getGreen()));
         sb.append(Integer.toHexString(color.getBlue()));
      }
      else {
         sb.append("none");
      }
      return sb.toString();
   }
   
   /**
    * Create an anchor for the given pattern.
    * The anchor is simply a rectangle based at (0, 0) with the height and width of the pattern.
    * @param pattern The pattern.
    * @returns The anchor.
    */
   static Rectangle2D getAnchor(Element pattern) {
      // get the bounds of the pattern and return it.
      return new Rectangle(0, 0, Integer.parseInt(pattern.getAttribute("width")), Integer.parseInt(pattern.getAttribute("height")));
   }      
   
   /**
    * ThreadLocal DocumentBuilder used to parse XML documents.
    */
   static ThreadLocal<DocumentBuilder> documentBuilder = new ThreadLocal() {
      public DocumentBuilder initialValue() {
         synchronized(DocumentBuilderFactory.class) {
            try {
               return DocumentBuilderFactory.newInstance().newDocumentBuilder();
            }
            catch (ParserConfigurationException pce) {
               throw new RuntimeException(pce);
            }
         }
      }
   };
     
   static TransformerFactory tFactory = TransformerFactory.newInstance();
}

mausbull
Posts: 15
Joined: Wed Feb 22, 2006 3:36 pm
Contact:

Post by mausbull » Fri Aug 08, 2008 12:19 pm

Hi,

although this thread is very old I still hope to get an answer.
Could you (jleech) or anyone else post an example on who to apply the patterns onto a chart.

Moreover I'd like to know if it is possible to apply different patterns for each bar in a barchart.

Thanks for your answers.

jleech
Posts: 62
Joined: Fri Oct 26, 2007 9:18 pm

Post by jleech » Mon Aug 11, 2008 10:20 pm

Sure, you can set the paint of most things in JFreeChart using some form of setPaint() on it. For a bar chart you've probably got a CategoryItemRenderer and you want to call setSeriesPaint(). The renderer belongs to the plot object. The code I posted makes the paint objects with the patterns, and if you render to SVG using Batik, provides a Batik extension to render the patterns in infinitely high resolution. If you want other patterns you can make them pretty easily from SVG.
mausbull wrote:Hi,

although this thread is very old I still hope to get an answer.
Could you (jleech) or anyone else post an example on who to apply the patterns onto a chart.

Moreover I'd like to know if it is possible to apply different patterns for each bar in a barchart.

Thanks for your answers.

bjohnso5
Posts: 8
Joined: Mon Nov 10, 2008 9:38 pm

Quick question...

Post by bjohnso5 » Mon Nov 10, 2008 9:41 pm

Hi jleech,

I am looking at modifying some of the code you posted to get patterns working with jFreeChart, but before I start working on it, I need to know if you have used any Java language features that are incompatible with 1.4.2.

Any info you can provide would be appreciated!

Thanks,

Bryan

jleech
Posts: 62
Joined: Fri Oct 26, 2007 9:18 pm

Re: Quick question...

Post by jleech » Mon Nov 10, 2008 10:35 pm

I don't see why it wouldn't work... just one class with a dependency on apache batik. I compiled with and run on 1.5. Compile it with 1.4 and see if there are errors. The only bug I'm aware of is in the posted colorToCSSHex() function, it needs to be modified to always output 6 hex digits.

-Jonathan
bjohnso5 wrote:Hi jleech,

I am looking at modifying some of the code you posted to get patterns working with jFreeChart, but before I start working on it, I need to know if you have used any Java language features that are incompatible with 1.4.2.

Any info you can provide would be appreciated!

Thanks,

Bryan

bjohnso5
Posts: 8
Joined: Mon Nov 10, 2008 9:38 pm

Having trouble

Post by bjohnso5 » Mon Nov 17, 2008 8:16 pm

Hi Jleech,

I'm wondering if you would be able to post a quick little example of your SVG pattern code working with a chart. I can't seem to get a pattern to render correctly.

Thanks!

- Bryan

jleech
Posts: 62
Joined: Fri Oct 26, 2007 9:18 pm

Re: Having trouble

Post by jleech » Mon Nov 17, 2008 9:07 pm

I don't have an example that I can cut and paste, but the general idea to output an SVG chart with SVG patterns is:

DefaultPieDataset dataset = new DefaultPieDataset();
JFreeChart chart = ChartFactory.createPieChart(...,dataset,...);
dataset.setValue(name1, value1);
plot.setSectionPaint(name1, SVGPatternTexturePaint.createSpotPaint(14, Color.BLACK, Color.WHITE));
... name2, value2
... name3, value3
SVGGraphics2D svgGenerator = SVGPatternTexturePaint.getSVGGraphics2D();
svgGenerator.setSVGCanvasSize(new Dimension(width, height));
chart.draw(svgGenerator, new Rectangle2D.Double(0, 0, width, height));
svgGenerator.stream(out, false); //out is an outputstream

It should use the patterns if you're not outputting SVG, just raster versions of them.

-Jonathan



bjohnso5 wrote:Hi Jleech,

I'm wondering if you would be able to post a quick little example of your SVG pattern code working with a chart. I can't seem to get a pattern to render correctly.

Thanks!

- Bryan

bjohnso5
Posts: 8
Joined: Mon Nov 10, 2008 9:38 pm

Great start

Post by bjohnso5 » Tue Nov 18, 2008 5:50 pm

Hi Jonathan,

I was able to get the patterns working with the pie chart example you stubbed out for me. I am trying to achieve a similar result with a bar chart, and I am not having any luck. I have attempted to set the series paint on the plot's renderer, but it does not change the output.

Is there anything specific that needs to be done to have this work with a bar chart?

Any info / ideas you have would be appreciated!

Thanks,

Bryan

jleech
Posts: 62
Joined: Fri Oct 26, 2007 9:18 pm

Re: Great start

Post by jleech » Tue Nov 18, 2008 6:05 pm

I use bar charts as well, Its pretty straightforward and like you said I call setSeriesPaint() on the renderer. I always use ChartFactory.createStackedBarChart3D() for my bar charts even if they aren't stacked. I don't see why what type of bar chart you create should matter but its worth a try. I also notice that I call setSeriesPaint(int, Paint) to set all the colors ahead of time, and to get a different color on each bar I have to add values to the dataset using dataset.addValue(value, name, name). Try making sure your code puts the correct colors on the bars as expected without textures.

-Jonathan

bjohnso5 wrote:Hi Jonathan,

I was able to get the patterns working with the pie chart example you stubbed out for me. I am trying to achieve a similar result with a bar chart, and I am not having any luck. I have attempted to set the series paint on the plot's renderer, but it does not change the output.

Is there anything specific that needs to be done to have this work with a bar chart?

Any info / ideas you have would be appreciated!

Thanks,

Bryan

bjohnso5
Posts: 8
Joined: Mon Nov 10, 2008 9:38 pm

Very strange

Post by bjohnso5 » Tue Nov 18, 2008 7:12 pm

Hi again,

It seems very strange to me, but when I use ChartFactory.createStackedBarChart3D(), it uses the patterns properly, but the same code does not change the bars for ChartFactory.createBarChart().

With that as literally the only change, do you know where I should be looking at this point? I have looked at the documentation, and they don't seem to be significantly different to me.

I should mention that using 3D charts is not an option at this point, so I really need to get this working on a regular, 2D chart.

Thanks,

Bryan

jleech
Posts: 62
Joined: Fri Oct 26, 2007 9:18 pm

Re: Very strange

Post by jleech » Tue Nov 18, 2008 7:23 pm

Bryan,

My best guess is the difference lies in the stacked vs. non-stacked and not in the 2D or 3D, and that your code isn't assigning the colors to the series in the right way for non-stacked bars, regardless of textures. You can try createStackedBarChart() instead of createStackedBarChart3D(), or look for an example of how to color the bars for a regular, non-stacked bar chart. Maybe its a limitation of JFreeChart.

-Jonathan
bjohnso5 wrote:Hi again,

It seems very strange to me, but when I use ChartFactory.createStackedBarChart3D(), it uses the patterns properly, but the same code does not change the bars for ChartFactory.createBarChart().

With that as literally the only change, do you know where I should be looking at this point? I have looked at the documentation, and they don't seem to be significantly different to me.

I should mention that using 3D charts is not an option at this point, so I really need to get this working on a regular, 2D chart.

Thanks,

Bryan

bjohnso5
Posts: 8
Joined: Mon Nov 10, 2008 9:38 pm

Post by bjohnso5 » Tue Nov 18, 2008 7:47 pm

I'm starting to wonder if the gradient paint is causing the problem for me. I've noticed that the 3D charts do not have any gradient painting on them, but the default BarChart does. I will attempt to remove it, and see if that gets me anywhere.

I'll let you know how it goes.

Thanks again for all your help, Jonathan.

Locked