StackedAreaRenderer modeling issue

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Post Reply
lemming622
Posts: 5
Joined: Wed Dec 05, 2018 8:15 pm
antibot: No, of course not.

StackedAreaRenderer modeling issue

Post by lemming622 » Tue Dec 11, 2018 7:50 pm

When my low point for a StackedAreaRender is not 0.0 the plot that is produced looks fine. The issue that I'm having comes in when I have data that has a low point of 0.0. The issue draws the filled area halfway between two categories and makes the plot look off.

Here is my code which is working fine and produces a stacked area plot I would expect.

Code: Select all


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;

import java.util.List;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.labels.StandardCategoryToolTipGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.AreaRendererEndType;
import org.jfree.chart.renderer.category.StackedAreaRenderer;
import org.jfree.chart.ui.ApplicationFrame;
import org.jfree.chart.ui.UIUtils;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.category.DefaultIntervalCategoryDataset;
import org.jfree.data.category.IntervalCategoryDataset;

/**
 *
 * @author buxk
 */
public class DifferenceWithCategories extends ApplicationFrame{
    
    
    public DifferenceWithCategories(String title){
        super(title);
        
        final JFreeChart chart = createChart();
        
        final ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new Dimension(500,270));
        setContentPane(chartPanel);
        
    }
    
    /**
     * Returns a sample dataset.
     *
     * @return The dataset.
     */
    private CategoryDataset createDataset1() {

        // row keys...
        String series1 = "First";

        // column keys...
        String category1 = "Category 1";
        String category2 = "Category 2";
        String category3 = "Category 3";
        String category4 = "Category 4";
        String category5 = "Category 5";

        // create the dataset...
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        
        dataset.addValue(4.0, series1, category1);
        dataset.addValue(4.0, series1, category2);
        dataset.addValue(3.0, series1, category3);
        dataset.addValue(5.0, series1, category4);
        dataset.addValue(5.0, series1, category5);
        
        return dataset;
    }

    
    /**
     * Returns a sample dataset.
     *
     * @return The dataset.
     */
    private CategoryDataset createDataset2() {

        // row keys...
        String series2 = "Second";
        
        // column keys...
        String category1 = "Category 1";
        String category2 = "Category 2";
        String category3 = "Category 3";
        String category4 = "Category 4";
        String category5 = "Category 5";

        // create the dataset...
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        
        dataset.addValue(5.0, series2, category1);
        dataset.addValue(7.0, series2, category2);
        dataset.addValue(6.0, series2, category3);
        dataset.addValue(8.0, series2, category4);
        dataset.addValue(6.0, series2, category5);
        
        return dataset;
    }

    
    /**
     * Returns a sample dataset.
     *
     * @return The dataset.
     */
    private CategoryDataset createDataset3() {

        // row keys...
        String series3 = "Third";

        // column keys...
        String category1 = "Category 1";
        String category2 = "Category 2";
        String category3 = "Category 3";
        String category4 = "Category 4";
        String category5 = "Category 5";

        // create the dataset...
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        
        dataset.addValue(1.0, series3, category1);
        dataset.addValue(3.0, series3, category2);
        dataset.addValue(6.0, series3, category3);
        dataset.addValue(3.0, series3, category4);
        dataset.addValue(4.0, series3, category5);
        
        return dataset;
    }
    
    private CategoryDataset merge4StackedDataset(CategoryDataset d1, CategoryDataset d2){
        
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        List<Comparable> columnKeys = d1.getColumnKeys();
        List<Comparable> d1RowKeys = d1.getRowKeys();
        List<Comparable> d2RowKeys = d2.getRowKeys();
        double minValue, maxValue;
        for (Comparable columnKey : columnKeys) {
            for (Comparable rowKey : d1RowKeys) {
                for (Comparable d2RowKey : d2RowKeys) {
                    minValue = Math.min(d1.getValue(rowKey, columnKey).doubleValue(),d2.getValue(d2RowKey, columnKey).doubleValue());
                    maxValue = Math.max(d1.getValue(rowKey, columnKey).doubleValue(),d2.getValue(d2RowKey, columnKey).doubleValue());
                    dataset.addValue(minValue, rowKey, columnKey);
                    dataset.addValue(maxValue - minValue, d2RowKey, columnKey);
//                    dataset.addValue(d2.getValue(d2RowKey, columnKey).doubleValue(), d2RowKey, columnKey);
                }
            }
            
        }
        
        return dataset;
    }

    private IntervalCategoryDataset createIntervalDataset(){
        double[] starts_S1 = new double[]{0.1, 0.2, 0.3};
        double[] starts_S2 = new double[]{0.3, 0.4, 0.5};
        double[] ends_S1 = new double[]{0.5, 0.6, 0.7};
        double[] ends_S2 = new double[]{0.7, 0.8, 0.9};
        double[][] starts = new double[][]{starts_S1, starts_S2};
        double[][] ends = new double[][]{ends_S1, ends_S2};
        DefaultIntervalCategoryDataset dataset
                = new DefaultIntervalCategoryDataset(starts, ends);
        return dataset;
    }
    
    private JFreeChart createChart() {
        
        CategoryDataset dataset1 = merge4StackedDataset(createDataset1(), createDataset2());
        CategoryDataset dataset2 = merge4StackedDataset(createDataset1(), createDataset3());

        //create chart with empty dataset
        final JFreeChart chart = ChartFactory.createAreaChart(
                "Testing",
                "Categories",
                "Values",
                null,
                PlotOrientation.VERTICAL,
                true,  // legend
                true,  // tool tips
                false  // URLs
        );
        
        //make the chart have a white background
        chart.setBackgroundPaint(Color.white);
        
        //get the chart's plot
        final CategoryPlot plot = chart.getCategoryPlot();
        
        //color the plot
        plot.setBackgroundPaint(Color.lightGray);
        plot.setDomainGridlinesVisible(true);
        plot.setDomainGridlinePaint(Color.white);
        plot.setRangeGridlinesVisible(true);
        plot.setRangeGridlinePaint(Color.white);
        
        plot.setDataset(dataset1);
        final StackedAreaRenderer renderer1 = new StackedAreaRenderer();
        renderer1.setEndType(AreaRendererEndType.LEVEL);
        renderer1.setDefaultToolTipGenerator(new StandardCategoryToolTipGenerator());
        renderer1.setSeriesPaint(0, colorWithAlpha(Color.lightGray, 0));
        renderer1.setSeriesVisibleInLegend(0, false);
        renderer1.setSeriesPaint(1, colorWithAlpha(Color.magenta, .5));
        plot.setRenderer(0, renderer1);
        plot.setRenderer(1, renderer1);
        
        
        plot.setDataset(2, dataset2);
        final StackedAreaRenderer renderer2 = new StackedAreaRenderer();
        renderer2.setEndType(AreaRendererEndType.LEVEL);
        renderer2.setDefaultToolTipGenerator(new StandardCategoryToolTipGenerator());
        renderer2.setSeriesPaint(0, colorWithAlpha(Color.lightGray, 0));
        renderer2.setSeriesVisibleInLegend(0, false);
        renderer2.setSeriesPaint(1, colorWithAlpha(Color.black, .5));
        plot.setRenderer(2, renderer2);
        plot.setRenderer(3, renderer2);

        
        plot.setDomainAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
        plot.getDomainAxis().setCategoryLabelPositions(CategoryLabelPositions.DOWN_45);
        plot.setDomainCrosshairVisible(true);
        plot.setRangeCrosshairVisible(true);
        
        return chart;
    }
    
    private Paint colorWithAlpha(Color color, double alpha){
        return new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) (alpha*255));
    }
    
    public static void main(String[] args) {
        final DifferenceWithCategories demo = new DifferenceWithCategories("Diference With Categories");
        demo.pack();
        UIUtils.centerFrameOnScreen(demo);
        demo.setVisible(true);
        
    }
}
When I change

Code: Select all

 dataset.addValue(4.0, series1, category1); 
to

Code: Select all

 dataset.addValue(0.0, series1, category1);
the area widens around the "0.0" value and produces a plot that is misleading. I have found that this only happens when a category goes to a 0 value.

Does anybody have a suggestion for how to fix this?

lemming622
Posts: 5
Joined: Wed Dec 05, 2018 8:15 pm
antibot: No, of course not.

Re: StackedAreaRenderer modeling issue

Post by lemming622 » Thu Dec 20, 2018 3:47 pm

I found a work around by setting the value to Math.ulp(1.0) to get a near zero value to get what I'm looking for. The source of the issue seem to come from the StackedAreaRender.drawItem method.

John Matthews
Posts: 464
Joined: Wed Sep 12, 2007 3:18 pm

Re: StackedAreaRenderer modeling issue

Post by John Matthews » Thu Dec 20, 2018 7:24 pm

Math.nextUp(0) is a smaller value (1.4E-45) that appears to sustain higher zooming.

lemming622
Posts: 5
Joined: Wed Dec 05, 2018 8:15 pm
antibot: No, of course not.

Re: StackedAreaRenderer modeling issue

Post by lemming622 » Fri Dec 21, 2018 3:37 pm

While yes 1.4e-45 is smaller than 2.2e-16, the level of zoom will be noticeably negligible. Any zooming will be lost when trying to actually see the data. If zooming is required then an implementation of the ItemLabelGenerator would easily assist with that.

Post Reply