3
votes

Overview

I've been trying to mimic the flot.js chart shown below in JFreeCharts.

enter image description here

so far I have succeeded in most elements such as individual bar colors with transparency, x and y grid lines and the background.

enter image description here

What I need to implement now are:

  • the individual bar border colors
  • solid black chart outline
  • highest y-axis value pushed to chart's top

What I've accomplished so far

Individual bar border colors

For the individual bar border colors, I found the thread "Adding border color/stroke to individual Bars in Bar Chart?" that suggested to use the code

Color borderColor = buildColorMap.get(build);
renderer.setDrawBarOutline(true);
renderer.setSeriesOutlinePaint(iterator, borderColor);

as opposed to the one I currently use which is for all bars

// other colors
renderer.setSeriesOutlinePaint(3, new Color(213, 94, 0));
renderer.setSeriesOutlinePaint(4, new Color(204, 121, 167));
renderer.setSeriesOutlineStroke(0, new BasicStroke(2.5f));

Chart outline

For the large outline for the chart, I saw setOutlineStroke but the API said

Deprecated. This method should no longer be used (as of version 1.0.6). It is sufficient to rely on setSeriesOutlineStroke(int, Stroke) and setBaseOutlineStroke(Stroke). Sets the outline stroke for ALL series and sends a RendererChangeEvent to all registered listeners.

Hence I went to setBaseOutlineStroke and nothing happened (still the same as the photo above)!

renderer.setBaseOutlineStroke(new BasicStroke(2.5f), true);

flot.js-like y-axis handling and positioning

For the y-axis numbering in flot.js, I was able to mimic its behavior of selecting the highest y-value's ten's ceiling as the upper limit of the chart using the functions (wherein the data are placed in a double array; there are only five instances so I do not need a more complex data structure):

private static double getHighestArrayValue(double[] array) {
    double max = 0;
    for (int i = 1; i < array.length; i++) {
        if (array[i] > max) {
            max = array[i];
        }
    }
    return max;
}

private static double getRoundedUpMultipleOfTen(double number) {
    return ((number + 9) / 10) * 10;
}

Wherein its usage to be the chart generated is:

CategoryPlot plot = ...

final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setRange(0, 
   getRoundedUpMultipleOfTen(getHighestArrayValue(dataValues)));
rangeAxis.setTickUnit(new NumberTickUnit(10));

resulting to

enter image description here

The problem now is how to push 50 to the top of the chart which I have no idea how to do.


Here is my full code for anyone interested (modified code of Viral Patel in "Generate Pie Chart/Bar Graph In PDF Using IText & JFreeChart":

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.geom.Rectangle2D;
import java.io.FileOutputStream;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.data.category.DefaultCategoryDataset;

import com.itextpdf.text.Document;
import com.itextpdf.text.Font;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;

public class ChartDemo {
    public static void main(String[] args) {
        writeChartToPDF(generateBarChart(), 800, 500, "D://barchart.pdf");
    }

    public static void writeChartToPDF(JFreeChart chart, int width, int height,
            String fileName) {
        PdfWriter writer = null;
        Document document = new Document(PageSize.A4.rotate(), 0, 0, 0, 0);

        try {
            writer = PdfWriter.getInstance(document, new FileOutputStream(
                    fileName));
            document.open();
            PdfContentByte contentByte = writer.getDirectContent();

            PdfTemplate template = contentByte.createTemplate(width, height);
            Graphics2D graphics2d = template.createGraphics(width, height);
            // controls size of image
            Rectangle2D rectangle2d = new Rectangle2D.Double(10, -10, width,
                    height);

            chart.draw(graphics2d, rectangle2d);

            graphics2d.dispose();
            contentByte.addTemplate(template, 0, 0);

        } catch (Exception e) {
            e.printStackTrace();
        }
        document.close();
    }

    static class CustomRenderer extends BarRenderer {

        private Paint[] colors;

        int transparency = 95;

        // http://www.cookbook-r.com/Graphs/Colors_(ggplot2)/
        public CustomRenderer() {
            this.colors = new Paint[] { new Color(1, 158, 115, transparency),
                    new Color(240, 228, 66, transparency),
                    new Color(0, 114, 178, transparency),
                    new Color(213, 94, 0, transparency),
                    new Color(204, 121, 167, transparency) };
        }

        public Paint getItemPaint(final int row, final int column) {
            return this.colors[column % this.colors.length];
        }
    }

    public static JFreeChart generateBarChart() {

        double[] dataValues = { 40.66, 37.82, 43.16, 32.62, 41.39 };
        Paint[] colors = { new Color(1, 158, 115), new Color(240, 228, 66),
                new Color(0, 114, 178), new Color(213, 94, 0),
                new Color(204, 121, 167) };

        DefaultCategoryDataset dataSet = new DefaultCategoryDataset();
        for (int i = 0; i < dataValues.length; i++) {
            dataSet.setValue(dataValues[i], "Population", (i + 1) * 2 + "");
        }

        ChartFactory.setChartTheme(StandardChartTheme.createLegacyTheme());
        JFreeChart chart = ChartFactory.createBarChart("", "Team number",
                "Solved problems (%)", dataSet, PlotOrientation.VERTICAL,
                false, true, false);
        chart.setBackgroundPaint(Color.WHITE);

        final CategoryPlot plot = chart.getCategoryPlot();
        ((BarRenderer) plot.getRenderer())
                .setBarPainter(new StandardBarPainter());

        plot.setBackgroundPaint(Color.WHITE);

        plot.setDomainGridlinesVisible(true);
        plot.setRangeGridlinesVisible(true);

        plot.setDomainGridlineStroke(new BasicStroke(0.25f));
        plot.setRangeGridlineStroke(new BasicStroke(0.25f));

        plot.setDomainGridlinePaint(new Color(204, 204, 204));
        plot.setRangeGridlinePaint(new Color(204, 204, 204));

        java.awt.Font fontGraphLabel = new java.awt.Font("Helvetica",
                Font.NORMAL, 12);
        java.awt.Font fontGraphTicks = new java.awt.Font("Helvetica",
                Font.NORMAL, 10);
        plot.getDomainAxis().setLabelFont(fontGraphLabel);
        plot.getRangeAxis().setLabelFont(fontGraphLabel);
        plot.getDomainAxis().setTickLabelFont(fontGraphTicks);
        plot.getRangeAxis().setTickLabelFont(fontGraphTicks);

        CategoryItemRenderer renderer = new CustomRenderer();
        plot.setRenderer(renderer);

        final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
        rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
        rangeAxis.setRange(0,
                getRoundedUpMultipleOfTen(getHighestArrayValue(dataValues)));
        rangeAxis.setTickUnit(new NumberTickUnit(10));

        final BarRenderer renderer1 = (BarRenderer) plot.getRenderer();
        renderer1.setDrawBarOutline(true);
        renderer1.setShadowVisible(false);

        renderer1.setSeriesOutlinePaint(0, new Color(204, 121, 167));
        renderer1.setSeriesOutlinePaint(1, new Color(213, 94, 0));
        renderer1.setSeriesOutlineStroke(0, new BasicStroke(2.5f));

        renderer1.setBaseOutlineStroke(new BasicStroke(10f), true);

        return chart;
    }

    private static double getHighestArrayValue(double[] array) {
        double max = 0;
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max) {
                max = array[i];
            }
        }
        return max;
    }

    private static double getRoundedUpMultipleOfTen(double number) {
        return ((number + 9) / 10) * 10;
    }

}
1
I'm just curious: why aren't you using Flot? - DNS
I'm actually using both Flot and JFreeChart. I have a requirement to generate a PDF report with a graph that has a close resemblance to the one implemented in the web :) - P A S T R Y
So you are replicating the plot to JFreeChart just to generate a PDF? Sounds to me like you are asking the wrong question. And making maintenance of the application a pain. - Mark
@Mark, yes but with other details of the report. Do you mind sharing what should I do with this (re application maintenance)? I don't know how to put the flot.js chart in my PDF - P A S T R Y
For an application I work on we use flot in both the web and on the PDF reports. To get it on the report, we generate the flot html/js and send it through phantomjs (phantomjs.org), outputting a png of each plot. This png is when included into the PDF. - Mark

1 Answers

2
votes

I'm not a Java guy but I'll give it a go with how I do it (all code untested)...

Say you have flot code that looks like this:

<div id="placeholder" style="width:300px;height:200px"></div>

$(function() {
    var series = {data: [[0, 5.2], [1, 3], [2, 9.2], [3, 10]],
                  lines: {show: false},
                  bars: {show: true, barWidth: 0.75, align:'center'}}

    somePlot = $.plot("#placeholder", [ series ], {});    
});

You need this code as a string, and then Base64 encode it (in a variable base64FlotCode).

Create a phantomJS script (saved as phantomJSScript.js) that looks like this:

var system = require('system');    
var html = system.args[1];    
var page = require('webpage').create();

page.onLoadFinished = function(status) {
    var clipRect = page.evaluate(
        function()
        { 
            var el = document.querySelector('#placeholder');
            return el.getBoundingClientRect();
        }
    );

    page.clipRect = {
        top:    clipRect.top,
        left:   clipRect.left,
        width:  clipRect.width,
        height: clipRect.height
    };
    page.render('/path/to/myPlot.png');

    phantom.exit();
};

function b64_to_utf8(str) {
    return decodeURIComponent(escape(window.atob(str)));
}

page.content = b64_to_utf8(html);

Now you can call this script as:

List<String> args = new ArrayList<String>();
args.add ("/path/to/phamtomjs.exe");
args.add ("/path/to/phantomJSScript.js");
args.add (base64FlotCode);
ProcessBuilder pb = new ProcessBuilder (args);
Process p = pb.start();
p.waitFor();

EDITS

Instead of passing phantomJS a string of your flot code, you could also point it to your webpage:

var system = require('system');      
var page = require('webpage').create();

page.open('http://www.mywebpage.com', function() {
    var clipRect = page.evaluate(
        function()
        { 
            var el = document.querySelector('#placeholder');
            return el.getBoundingClientRect();
        }
    );

    page.clipRect = {
        top:    clipRect.top,
        left:   clipRect.left,
        width:  clipRect.width,
        height: clipRect.height
    };
    page.render('/path/to/myPlot.png');

    phantom.exit();
};