Search code examples
javagraphicsjfreechartrenderer

Creating an area graph below a XYDifference(Renderer) graph


I have been trying for the last week to find a way to make JFreeChart display something similar to the image below. Basically you are looking at three series (upper, middle, lower) with a fill inbetween. And underneath there is a (light green) fill color, or an area chart as some would perhaps call it - no meaning, just for looks.

enter image description here

The only thing really missing from what I have come up with is the last part: the fill underneath / area chart:

enter image description here

I even tried to subclass XYDifferenceRenderer and combine it with the renderer for Areachart, but I could not control the height of the areachart, basically filling up the plot to the top. So that was a no-go. Having created as simple rendererer to create rounded bar charts earlier, I thought that I might be able to change the code for XYDifferenceRenderer. But the code for XYDifferenceRenderer is quite a handful of geometry and inner workings of JFree chart, and the task was a bit overwhelming. So any tips on how to achieve this effect in any "normal" way (that does not involve hacking JFreeChart's inner workings)?


Solution

  • Found an old post describing how to use two renderers in the same plot, which was just the thing in this case.

    To get a fill underneath you need to

    1. create two new series
      • one is the lower bound of the difference plot
      • the other is the values at the bottom of the plot - often just zero. Easily got by calling plot.getRangeAxis().getLowerBound()
    2. add them to a new dataset and add this to the plot I was unaware that a plot could have several datasets. Turns out one can just use an index to access them.
    3. create a new renderer for the "fill" dataset
      • create a new renderer
      • set the right fill paint
      • set the rendererer for the new dataset to be the new renderer

    The code is something akin to the following, where the fill Paint obviously is up to you:

    static void addFill(Plot plot) {
    
        XYSeries lowerLimitSeries = ((XYSeriesCollection) (plot.getDataset())).getSeries(1);
        XYSeriesCollection fillSet = new XYSeriesCollection();
        double lowerBound = plot.getRangeAxis().getLowerBound();
        fillSet.addSeries(lowerLimitSeries);
        fillSet.addSeries(createLowerFillSeries(lowerLimitSeries, lowerBound));
        plot.setDataset(1, fillSet);
        Paint fillPaint = Color.GREEN;
        XYDifferenceRenderer fillRenderer = new XYDifferenceRenderer(fillPaint, fillPaint, false);
        fillRenderer.setSeriesStroke(0, new BasicStroke(0)); //do not show
        fillRenderer.setSeriesStroke(1, new BasicStroke(0)); //do not show
        plot.setRenderer(1, fillRenderer);
        ...
    }
    
    static XYSeries createLowerFillSeries(XYSeries lowerLimitSeries, double lowerLimit) {
        int size = lowerLimitSeries.getItems().size();
        XYSeries res = new XYSeries("lowerFillSeries");
        for (int i = 0; i < size; i++) res.add(new XYDataItem(lowerLimitSeries.getX(i), lowerLimit));
        return res;
    }