Scrolling Graph example ..

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
moonblade
Posts: 21
Joined: Mon Sep 18, 2006 9:41 am

Scrolling Graph example ..

Post by moonblade » Wed Sep 27, 2006 5:11 am

Hi all,

Does anyone have a working example for scrolling graph ?
For example, i would like to scroll gantt chart horizontally if it doesnt fit specific area, also with the fixed tick dimension.

Also, How do i set the auto fit off ? For example i want to set the tick size to represent weeks, and should have the fixed width of 20 pixel.
Please point me the starting point, what should i change or override ..

Thanks before ^^

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Thu Sep 28, 2006 11:33 am

Here is a demo that translates data values using a control (JSlider) external to the chart. Each update requires a repaint of the chart, but I don't think there is a way to escape that with JFreeChart.

You could modify this so that the JSlider updates the axis range, rather than the dataset - that would work for scrolling.

Code: Select all

/* -------------------
 * TranslateDemo1.java
 * -------------------
 * (C) Copyright 2006, by Object Refinery Limited.
 *
 */

package demo;

import java.awt.BorderLayout;
import java.awt.Color;

import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.Range;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.DatasetChangeListener;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.data.time.Minute;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.AbstractXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.RefineryUtilities;

/**
 * A demo that uses a "wrapper" dataset that provides a translation of the 
 * underlying dataset.
 */
public class TranslateDemo1 extends ApplicationFrame {

    private static class DemoPanel extends JPanel 
                                   implements ChangeListener {
    
        private TimeSeries series;
        
        private ChartPanel chartPanel;
        
        private JFreeChart chart;
        
        private JSlider slider;
        
        private TranslatingXYDataset dataset;

        static class TranslatingXYDataset extends AbstractXYDataset 
                implements XYDataset, DatasetChangeListener {
            private XYDataset underlying;
            private double translate;
            
            /**
             * Creates a new <code>TranslatingXYDataset</code> class that
             * applies a dynamically updateable translation to the underlying 
             * dataset.
             * 
             * @param underlying  the underlying dataset (<code>null</code> not 
             *     permitted).
             */
            public TranslatingXYDataset(XYDataset underlying) {
                this.underlying = underlying;
                this.underlying.addChangeListener(this);
                this.translate = 0.0;
            }
            
            /**
             * Returns the current translation factor.
             * 
             * @return The translation factor.
             */
            public double getTranslate() {
                return this.translate;
            }
            
            /**
             * Sets the translation factor.
             * 
             * @param t  the translation factor.
             */
            public void setTranslate(double t) {
                this.translate = t;
                fireDatasetChanged();
            }
            public int getItemCount(int series) {
                return this.underlying.getItemCount(series);
            }
            public double getXValue(int series, int item) {
                return this.underlying.getXValue(series, item) + translate;
            }
            public Number getX(int series, int item) {
                return new Double(getXValue(series, item));
            }
            public Number getY(int series, int item) {
                return new Double(getYValue(series, item));
            }
            public double getYValue(int series, int item) {
                return this.underlying.getYValue(series, item);
            }
            public int getSeriesCount() {
                return this.underlying.getSeriesCount();
            }
            public Comparable getSeriesKey(int series) {
                return underlying.getSeriesKey(series);
            }
            public void datasetChanged(DatasetChangeEvent event) {
                // underlying dataset has changed, so notify our listeners
                this.fireDatasetChanged();
            }
        }
        
        /**
         * Creates a new demo panel.
         */
        public DemoPanel() {
            super(new BorderLayout());
            this.chart = createChart();
            this.chartPanel = new ChartPanel(this.chart);
            this.chartPanel.setPreferredSize(new java.awt.Dimension(600, 270));
            this.chartPanel.setDomainZoomable(true);
            this.chartPanel.setRangeZoomable(true);
            Border border = BorderFactory.createCompoundBorder(
                BorderFactory.createEmptyBorder(4, 4, 4, 4),
                BorderFactory.createEtchedBorder()
            );
            this.chartPanel.setBorder(border);
            add(this.chartPanel);
            
            JPanel dashboard = new JPanel(new BorderLayout());
            dashboard.setBorder(BorderFactory.createEmptyBorder(0, 4, 4, 4));   
            // make the slider units "minutes"
            this.slider = new JSlider(-200, 200, 0);
            slider.setPaintLabels(true);
            slider.setMajorTickSpacing(50);
            slider.setPaintTicks(true);
            this.slider.addChangeListener(this);
            dashboard.add(this.slider);
            add(dashboard, BorderLayout.SOUTH);
        }
        
        /**
         * Creates the demo chart.
         * 
         * @return The chart.
         */
        private JFreeChart createChart() {

            XYDataset dataset1 = createDataset(
                "Random 1", 100.0, new Minute(), 200
            );
            
            JFreeChart chart1 = ChartFactory.createTimeSeriesChart(
                "Translate Demo 1", 
                "Time of Day", 
                "Value",
                dataset1, 
                true, 
                true, 
                false
            );

            chart1.setBackgroundPaint(Color.white);
            XYPlot plot = chart1.getXYPlot();
            plot.setOrientation(PlotOrientation.VERTICAL);
            plot.setBackgroundPaint(Color.lightGray);
            plot.setDomainGridlinePaint(Color.white);
            plot.setRangeGridlinePaint(Color.white);
            plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
            
            plot.setDomainCrosshairVisible(true);
            plot.setDomainCrosshairLockedOnData(false);
            plot.setRangeCrosshairVisible(false);
            XYItemRenderer renderer = plot.getRenderer();
            renderer.setPaint(Color.black);
            // fix the range
            DateAxis axis = (DateAxis) plot.getDomainAxis();
            Range range = DatasetUtilities.findDomainBounds(dataset);
            axis.setRange(range);
            return chart1;
        }
        
        
        /**
         * Creates a sample dataset.
         * 
         * @param name  the dataset name.
         * @param base  the starting value.
         * @param start  the starting period.
         * @param count  the number of values to generate.
         *
         * @return The dataset.
         */
        private XYDataset createDataset(String name, double base, 
                                        RegularTimePeriod start, int count) {

            this.series = new TimeSeries(name, start.getClass());
            RegularTimePeriod period = start;
            double value = base;
            for (int i = 0; i < count; i++) {
                this.series.add(period, value);    
                period = period.next();
                value = value * (1 + (Math.random() - 0.495) / 10.0);
            }

            TimeSeriesCollection tsc = new TimeSeriesCollection();
            tsc.addSeries(this.series);
            this.dataset = new TranslatingXYDataset(tsc);
            return dataset;

        }
        
        /**
         * Handles a state change event.
         * 
         * @param event  the event.
         */
        public void stateChanged(ChangeEvent event) {
            int value = this.slider.getValue();
            // value is in minutes
            this.dataset.setTranslate(value * 60 * 1000.0);
        }

    }
    
    /**
     * A demonstration application showing how to control a crosshair using an
     * external UI component.
     *
     * @param title  the frame title.
     */
    public TranslateDemo1(String title) {
        super(title);
        setContentPane(new DemoPanel());
    }

    /**
     * Creates a panel for the demo (used by SuperDemo.java).
     * 
     * @return A panel.
     */
    public static JPanel createDemoPanel() {
        return new DemoPanel();
    }

    /**
     * Starting point for the demonstration application.
     *
     * @param args  ignored.
     */
    public static void main(String[] args) {

        TranslateDemo1 demo = new TranslateDemo1("Translate Demo 1");
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);

    }
    
}
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

moonblade
Posts: 21
Joined: Mon Sep 18, 2006 9:41 am

Post by moonblade » Fri Sep 29, 2006 10:41 am

Thanks alot for the help.
I'm still experimenting ...
Wish me good luck, :D

moonblade
Posts: 21
Joined: Mon Sep 18, 2006 9:41 am

Post by moonblade » Tue Oct 03, 2006 5:26 am

Hi all,

The scrolling works marvelously ..
But ..
How could i possibly have a fixed tick space, for example like 20 pixel .. or perhaps i could control that 1 graph could only have specific amount of ticks ?

Now the workaround that i have is to set the range for the range axis, so that it affects the number of ticks displayed.

Please guide me again !
:D

moonblade
Posts: 21
Joined: Mon Sep 18, 2006 9:41 am

Post by moonblade » Tue Oct 03, 2006 7:13 am

Aha !
I think this should help ..

Time to experiment some more ..

moonblade
Posts: 21
Joined: Mon Sep 18, 2006 9:41 am

Post by moonblade » Tue Oct 03, 2006 12:32 pm

Is it possible to have the domain axis in your example be vertically scrollable ?
Btw the horizontal scroll works fine now in the gantt chart example ..
Thanks alot !
:D

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Wed Oct 04, 2006 10:01 am

Do you mean an additional slider that controls the vertical axis? Yes, that would be possible to add.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

moonblade
Posts: 21
Joined: Mon Sep 18, 2006 9:41 am

Post by moonblade » Wed Oct 04, 2006 12:05 pm

Thanks for the reply ..

I would like to describe more about this matter.

For example, in the Gantt2Demo.java, there are around 12 tasks at the domain axis, like the 'write proposal', 'obtain proposal' etc ..

In the example all the tasks are viewable. If i want only the first five task viewable .. and would like to scroll down to view the rest, is this possible ?

In the example for horizontal scrolling, i could use plot.getRangeAxis().setRange ...

But for plot.getDomainAxis(), there is no setRange method .. or at least to my knowledge, hehe :oops:

Please point me the way ..

mathematical
Posts: 13
Joined: Mon Apr 30, 2007 3:24 pm

Post by mathematical » Tue May 15, 2007 3:46 pm

I'm also stuck here with a getDomainAxis() but no setDomain() method. I'm trying to make my graph vertically scrollable using the JSlider class as used in previous posts.

So heres my question, is there a way to vertically scroll a category bar graph with a JSlider using the getDomainAxis() method?

mathematical
Posts: 13
Joined: Mon Apr 30, 2007 3:24 pm

Post by mathematical » Tue May 15, 2007 10:26 pm

ok no response, lets try again...I'll ask a better question this time.

How would I go about subclassing the CategoryAxis class to give it a way to be updated by a JSlider object? It looks like its very involved but I'm willing to get my hands dirty to solve this issue. I cant be the only one working on this.

Heres my idea:

1. Maybe I could give this CustomCategoryAxis class a numeric field that represents a percentage and if the percentage is over 100 then that means that scrolling is needed. This percentage can be attained by adding the bar margins and etc.

If you feel that there is a better approach to achieve vertical scrolling on a horizontal bar graph, then let me know.

moonblade
Posts: 21
Joined: Mon Sep 18, 2006 9:41 am

Post by moonblade » Thu May 17, 2007 6:45 am

As for my case, i switched to another solution. At that time, i didnt have the time to invest on finding solution on the vertical scolling, so i implemented paging.

Hope you could find the solution, and dont give up !

Cheers,
Albert Kam

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Thu May 17, 2007 2:07 pm

I think it would be good to have a CategoryAxis implementation where you can specify the exact categories to be displayed (including their order). That would allow both reordering the displayed items without modifying the dataset, and displaying subsets of the items in the dataset (which would be a starting point for a "scrollable" axis implementation).

Right now, I think the CategoryPlot and CategoryAxis interactions rely on index values too much, so there would be a lot of refactoring required to make this work. I'm not sure whether it can be done while preserving the existing API. If not, it would still be worth investigating for inclusion in the next major version bump (whenever that might be).

This is something I'm interested in, but don't have time to work on right now.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

RichardWest
Posts: 844
Joined: Fri Oct 13, 2006 9:29 pm
Location: Sunnyvale, CA

Post by RichardWest » Fri May 18, 2007 4:41 am

david.gilbert wrote:I'm not sure whether it can be done while preserving the existing API. If not, it would still be worth investigating for inclusion in the next major version bump (whenever that might be).

This is something I'm interested in, but don't have time to work on right now.
While this would be a major underlying rewrite of the code, it would be possible to store all the data in Map and Set objects. This would allow the user to specify random, in-order, or sorted-order storage depending upon the options they choose and the specific type of Map or Set objects constructed. The general methods of the Map and Set classes would allow a lot of flexibility. I would also be possible to preserve the current API (maybe deprecate the functions for future removal) buy using LinkedHashMap and LinkedSet objects and converting them to arrays with the toArray(Object []) method.

I recently did this with my application. It was a major pain to do, but it was much more flexible afterwards. Thankfully, I did not have to worry so much about maintaining the old API.
Richard West
Design Engineer II
Advanced Micro Devices
Sunnyvale, CA

mathematical
Posts: 13
Joined: Mon Apr 30, 2007 3:24 pm

Post by mathematical » Fri May 18, 2007 1:47 pm

Heres the approach that I'm taking...

I created a class called ScrollableCategoryPlot and so far I added the following fields:

Code: Select all

//code...
private ObjectList totalDatasets;
private int numberOfCategoriesDisplayed;
private int numberOfCategoriesSkipped;
//more code...
So far I've written their get and set methods but I still dont have a working piece yet. Also I didnt extend the CategoryPlot class, I just rewrote it with those modifications and I'll try my best not to break the current API. I'll post my code when its finished.

My idea is that I can achieve scrolling by using the original datasets field to only store the data that I want to be displayed and in the totalDatasets field I store all of the data. Now with the numberOfCategoriesDisplayed, I can controll how many categories to display and with the numberOfCategoriesSkipped, I can controll the number of categories skipped. The numberOfCategoriesSkipped will change any time you scroll and as it changes it will modify the chart that is currently displayed, hence scrolling.

And yes there was A LOT of refactoring but in the end it will be worth it.

So thats my idea, and please reply with your thoughts and comments. I will get back to work.

mathematical
Posts: 13
Joined: Mon Apr 30, 2007 3:24 pm

Post by mathematical » Tue May 22, 2007 4:47 pm

After a lot of hard work, I got the results that I wanted. I admit that this is not the best solution and it's more of a work around but non the less it did solve the issue.

My final approach to attain a scrollable graph was to create an interface titled ScrollablePlot and create a ScrollableCategoryPlot class that extends CategoryPlot and implements the ScrollablePlot interface. The purpose of the interface is to allow other plot classes to implement it and attain scrollable functionality as well. Then I created a ScrollableChartPane class that extends JPanel and implements AdjustmentListener and MouseWheelListener. The ScrollableChartPane class allows you to scroll with the scroll bar or scroll with the mouse wheel. I also added some extra goodies like background image tabs that make the displayed data a lot easier to read.

If you decide to create your own ScrollableXYZPlot class, you should implement the ScrollablePlot interface and add code to the constructor and the implemented methods of ScrollableChartPane. Please let me know what you think about the code.

Hopefully this code can be enhanced in a way so that it can be applied to any other plot class.

ScrollablePlot interface...

Code: Select all

public interface ScrollablePlot 
{
	public void setScrollBar(JScrollBar scrollBar);
	public void scrollBarValueChanged(AdjustmentEvent event);
	public void mouseWheelScrolled(MouseWheelEvent event);
}
ScrollableCategoryPlot class...

Code: Select all

public class ScrollableCategoryPlot extends CategoryPlot implements ScrollablePlot
{	
	private static final long serialVersionUID = 4760746506864409126L;
	private static final int DEFAULT_BACKGROUND_IMAGE_HEIGHT = 316;
	private static final int DEFAULT_BACKGROUND_IMAGE_WIDTH = 558;
	private static final int DEFAULT_BACKGROUND_TAB_COLOR = 0x00bfffff;
	private static final int DEFAULT_BACKGROUND_IMAGE_COLOR = 0x00ffffff;
	private static final int DEFALUT_BAR_OFFSET_PIXELS = 8;
	private static final double DEFAULT_BAR_WIDTH_PERCENT = 0.06;
    private static final double DEFAULT_BAR_SPACE_PERCENT = 0.02;
    private static final double DEFAULT_CATEGORY_SPACE_PERCENT = 0.05;
    private static final double DEFAULT_TOP_MARGIN_PERCENT = 0.05;
           
    private JScrollBar scrollBar;
	private ObjectList totalDatasets;    
    private int numberOfCategoriesDisplayed;
    private int numberOfCategoriesSkipped;
    
    private int backgroundImageHeight;
    private int backgroundImageWidth;
    private int backgroundTabColor;
    private int backgroundImageColor;
    private int barOffsetPixels;
    private double barWidthPercent;
    private double barSpacePercent;
    private double categorySpacePercent;
    private double topMarginPercent;

    public ScrollableCategoryPlot()
    {
    	this(null, null, null, null);
    }
    
    public ScrollableCategoryPlot(CategoryDataset dataset, CategoryAxis domainAxis, ValueAxis rangeAxis, CategoryItemRenderer renderer)
    {
    	super(dataset, domainAxis, rangeAxis, renderer);
    	this.totalDatasets = new ObjectList();
        this.totalDatasets.set(0, dataset);
        this.numberOfCategoriesDisplayed = -1; //all
        this.numberOfCategoriesSkipped = 0;
        this.backgroundImageHeight = ScrollableCategoryPlot.DEFAULT_BACKGROUND_IMAGE_HEIGHT;
        this.backgroundImageWidth = ScrollableCategoryPlot.DEFAULT_BACKGROUND_IMAGE_WIDTH;
        this.backgroundTabColor = ScrollableCategoryPlot.DEFAULT_BACKGROUND_TAB_COLOR;
        this.backgroundImageColor = ScrollableCategoryPlot.DEFAULT_BACKGROUND_IMAGE_COLOR;
        this.barOffsetPixels = ScrollableCategoryPlot.DEFALUT_BAR_OFFSET_PIXELS;
        this.barWidthPercent = ScrollableCategoryPlot.DEFAULT_BAR_WIDTH_PERCENT;
        this.barSpacePercent = ScrollableCategoryPlot.DEFAULT_BAR_SPACE_PERCENT;
        this.categorySpacePercent = ScrollableCategoryPlot.DEFAULT_CATEGORY_SPACE_PERCENT;
        this.topMarginPercent = ScrollableCategoryPlot.DEFAULT_TOP_MARGIN_PERCENT;   
        
        this.setOrientation(PlotOrientation.HORIZONTAL);
		this.setRowRenderingOrder(SortOrder.DESCENDING);
        this.setColumnRenderingOrder(SortOrder.DESCENDING);
        this.setOutlinePaint(null);
		this.setForegroundAlpha((float)1.0);
		this.setBackgroundPaint(Color.WHITE);
		
		//position the y axis labels
		CategoryLabelPosition left = new CategoryLabelPosition
		(
			RectangleAnchor.LEFT, TextBlockAnchor.CENTER_LEFT, 
	        TextAnchor.CENTER_LEFT, 0.0,
	        CategoryLabelWidthType.RANGE, 0.30f
	    );
	    this.getDomainAxis().setCategoryLabelPositions(CategoryLabelPositions.replaceLeftPosition(this.getDomainAxis().getCategoryLabelPositions(), left));
	   
    }
    
    public void setScrollBar(JScrollBar scrollBar)
    {
    	this.scrollBar = scrollBar;
    }
    
    public int getNumberOfCategoriesDisplayed()
    {
    	return numberOfCategoriesDisplayed;
    }
    
    public void setNumberOfCategoriesDisplayed(int categoriesDisplayed)
    {
    	if (categoriesDisplayed < 0)
    	{
    		this.numberOfCategoriesDisplayed = -1;
    	}
    	else
    	{
    		this.numberOfCategoriesDisplayed = categoriesDisplayed;
    	}
    }
    
    public int getNumberOfCategoriesSkipped()
    {
    	return this.numberOfCategoriesSkipped;
    }
    
    public void setNumberOfCategoriesSkipped(int categoriesSkipped)
    {
    	if (categoriesSkipped > 0)
    	{
    		this.numberOfCategoriesSkipped = categoriesSkipped;    		
    	}
    	else
    	{
    		this.numberOfCategoriesSkipped = 0;
    	}
    }
    
    public CategoryDataset getTotalDataset() 
    {
        CategoryDataset result = null;
        if (this.totalDatasets.size() > 0) 
        {
            result = (CategoryDataset) this.totalDatasets.get(0);
        }
        return result;
    }    
    
    public void setTotalDataset(CategoryDataset totalDataset)
    {
    	this.setNumberOfCategoriesSkipped(0);
    	this.totalDatasets.set(0, totalDataset);
    	this.setDisplayedDataset();   	
    		
    	if (scrollBar != null)
		{
			scrollBar.setMinimum(this.getScrollMinimumValue());
			scrollBar.setMaximum(this.getScrollMaximumValue());
			if (this.getNumberOfCategoriesDisplayed() != -1)
			{
				scrollBar.setVisibleAmount(this.getNumberOfCategoriesDisplayed());
			}
		}
    }
        
    private void setDisplayedDataset()
    {
    	DefaultCategoryDataset displayedDataset = new DefaultCategoryDataset();
    	int categoriesDisplayed = 0;
    	if (this.getTotalDataset() != null)
    	{    		
    		if ((this.numberOfCategoriesDisplayed != -1)&&(this.numberOfCategoriesSkipped + this.numberOfCategoriesDisplayed <= this.getTotalDataset().getColumnCount()))
    		{
    			categoriesDisplayed = this.numberOfCategoriesSkipped + this.numberOfCategoriesDisplayed;
    		}
    		else
    		{
    			categoriesDisplayed = this.getTotalDataset().getColumnCount();
    		}
    		if (this.numberOfCategoriesSkipped < categoriesDisplayed)
    		{
    			for (int column = this.numberOfCategoriesSkipped; column < categoriesDisplayed; column++)
        		{
        			for (int row = 0; row < this.getTotalDataset().getRowCount(); row++)
        			{
        				displayedDataset.addValue(this.getTotalDataset().getValue(row, column), this.getTotalDataset().getRowKey(row), this.getTotalDataset().getColumnKey(column));
        			}
        		}
    		}
    	}    	
    	this.setDataset(displayedDataset);
    	this.adjustDisplay();
    }    
    
    public int getBackgroundImageHeight()
    {
    	return this.backgroundImageHeight;
    }
    
    public void setBackgroundImageHeight(int backgroundHeight)
    {
    	if (backgroundHeight > 0)
    	{
    		this.backgroundImageHeight = backgroundHeight;
    	}
    	else
    	{
    		this.backgroundImageHeight = ScrollableCategoryPlot.DEFAULT_BACKGROUND_IMAGE_HEIGHT;
    	}
    }
    
    public int getBackgroundImageWidth()
    {
    	return this.backgroundImageWidth;
    }
    
    public void setBackgroundImageWidth(int backgroundWidth)
    {
    	if (backgroundWidth > 0)
    	{
    		this.backgroundImageWidth = backgroundWidth;
    	}
    	else
    	{
    		this.backgroundImageWidth = ScrollableCategoryPlot.DEFAULT_BACKGROUND_IMAGE_WIDTH;
    	}
    }
    
    public int getBackgroundTabColor()
    {
    	return this.backgroundTabColor;
    }
    
    public void setBackgroundTabColor(int tabColor)
    {
    	if (tabColor < 0)
    	{
    		this.backgroundTabColor = ScrollableCategoryPlot.DEFAULT_BACKGROUND_TAB_COLOR;
    	}
    	else
    	{
    		this.backgroundTabColor = tabColor;
    	}    	
    }
    
    public void setBackgroundTabColor(int alpha, int red, int green, int blue)
    {
    	if ((alpha >= 0)&&(alpha <= 255)&&(red >= 0)&&(red <= 255)&&(green >= 0)&&(green <= 255)&&(blue >= 0)&&(blue <= 255))
    	{
    		int tabColor = (alpha << 24) + (red << 16) + (green << 8) + (blue);    		
    		this.setBackgroundTabColor(tabColor);
    	}
    	else
    	{
    		this.backgroundTabColor = ScrollableCategoryPlot.DEFAULT_BACKGROUND_TAB_COLOR;
    	} 
    }
    
    public int getBackgroundImageColor()
    {
    	return this.backgroundImageColor;
    }
    
    public void setBackgroundImageColor(int imageColor)
    {
    	if (imageColor < 0)
    	{
    		this.backgroundImageColor = ScrollableCategoryPlot.DEFAULT_BACKGROUND_IMAGE_COLOR;
    	}
    	else
    	{
    		this.backgroundImageColor = imageColor;
    	}   
    }
    
    public void setBackgroundImageColor(int alpha, int red, int green, int blue)
    {
    	if ((alpha >= 0)&&(alpha <= 255)&&(red >= 0)&&(red <= 255)&&(green >= 0)&&(green <= 255)&&(blue >= 0)&&(blue <= 255))
    	{
    		int imageColor = (alpha << 24) + (red << 16) + (green << 8) + (blue);    		
    		this.setBackgroundImageColor(imageColor);
    	}
    	else
    	{
    		this.backgroundImageColor = ScrollableCategoryPlot.DEFAULT_BACKGROUND_IMAGE_COLOR;
    	} 
    }
    
    public int getBarOffsetPixels()
    {
    	return this.barOffsetPixels;
    }
    
    public void setBarOffsetPixels(int offsetPixels)
    {
    	if (offsetPixels >= 0)
    	{
    		this.barOffsetPixels = offsetPixels;
    	}
    	else
    	{
    		this.barOffsetPixels = ScrollableCategoryPlot.DEFALUT_BAR_OFFSET_PIXELS;
    	}
    }
    
    public double getBarWidthPercent()
    {
    	return this.barWidthPercent;
    }
    
    public void setBarWidthPercent(double barWidth)
    {
    	if ((barWidth > 0.0)&&(barWidth < 1.0))
    	{
    		this.barWidthPercent = barWidth;
    	}
    	else
    	{
    		this.barWidthPercent = ScrollableCategoryPlot.DEFAULT_BAR_WIDTH_PERCENT;
    	}
    }
    
    public double getBarSpacePercent()
    {
    	return this.barSpacePercent;
    }
    
    public void setBarSpacePercent(double barSpace)
    {
    	if ((barSpace > 0.0)&&(barSpace < 1.0))
    	{
    		this.barSpacePercent = barSpace;
    	}
    	else
    	{
    		this.barSpacePercent = ScrollableCategoryPlot.DEFAULT_BAR_SPACE_PERCENT;
    	}
    }
    
    public double getCategorySpacePercent()
    {
    	return this.categorySpacePercent;
    }
    
    public void setCategorySpacePercent(double categorySpace)
    {
    	if ((categorySpace > 0.0)&&(categorySpace < 1.0))
    	{
    		this.categorySpacePercent = categorySpace;
    	}
    	else
    	{
    		this.categorySpacePercent = ScrollableCategoryPlot.DEFAULT_CATEGORY_SPACE_PERCENT;
    	}
    }
    
    public double getTopMarginPercent()
    {
    	return this.topMarginPercent;
    }
    
    public void setTopMarginPercent(double topMargin)
    {
    	if ((topMargin > 0.0)&&(topMargin < 1.0))
    	{
    		this.topMarginPercent = topMargin;
    	}
    	else
    	{
    		this.topMarginPercent = ScrollableCategoryPlot.DEFAULT_TOP_MARGIN_PERCENT;
    	}
    }
    
    private void adjustDisplay()
    {
    	if (this.getDataset().getColumnCount() > 0)
    	{
    		double extraSpace = 0.0;
    		BarRenderer3D renderer = (BarRenderer3D)this.getRenderer();
    		renderer.setMaximumBarWidth(this.getBarWidthPercent());
    		renderer.setItemMargin(this.getBarSpacePercent() * this.getDataset().getColumnCount() * (this.getDataset().getRowCount() - 1));
    		this.getDomainAxis().setLowerMargin(this.getTopMarginPercent());
    		this.getDomainAxis().setCategoryMargin(this.getCategorySpacePercent() * (this.getDataset().getColumnCount() - 1)); 

    		extraSpace = 1 - (this.getDomainAxis().getLowerMargin() + this.getDomainAxis().getCategoryMargin() + renderer.getItemMargin() + (renderer.getMaximumBarWidth() * this.getDataset().getColumnCount() * this.getDataset().getRowCount()));
    		this.getDomainAxis().setUpperMargin(extraSpace);
    		this.setBackgroundImage(this.getTabbedBackgroundImage());
    	}
    	else
    	{
    		this.setBackgroundImage(this.getColoredBackgroundImage());
    	}
    }
    
    private Image getColoredBackgroundImage()
	{		
        Image image;
        int imageHeight = this.getBackgroundImageHeight(); 
        int imageWidth = this.getBackgroundImageWidth();            
        int[] imagePixels = new int[imageHeight * imageWidth];                
        for (int r = 0, i = 0; r < imageHeight; r++)
        {        	
        	for (int c = 0; c < imageWidth; c++)
        	{        		       		
        		imagePixels[i++] = this.getBackgroundImageColor(); 
        	}        	
        }
        Container container = new Container();
        image = container.createImage(new MemoryImageSource(imageWidth, imageHeight, imagePixels, 0, imageWidth));
        return image;
	}
    
    private Image getTabbedBackgroundImage()
	{		
        Image image;
        int imageHeight = this.getBackgroundImageHeight(); 
        int imageWidth = this.getBackgroundImageWidth();           
        int color = 0; 
        int count = 0;
        int[] imagePixels = new int[imageHeight * imageWidth];   
        double pixelStart = this.getTopMarginPercent() * imageHeight;
        double pixelEnd = ((this.getBarWidthPercent() * this.getTotalDataset().getRowCount()) + (this.getBarSpacePercent() * (this.getTotalDataset().getRowCount() - 1))) * imageHeight + pixelStart + this.getBarOffsetPixels();
        boolean changePixelRange = false;
        for (int r = 0, i = 0; r < imageHeight; r++)
        {        	
        	for (int c = 0; c < imageWidth; c++)
        	{
        		if ((r >= pixelStart)&&(r <= pixelEnd)&&(count < this.getDataset().getColumnCount()))
        		{        			
        			color = this.getBackgroundTabColor();
        			changePixelRange = true;        			
        		}
        		else
        		{
        			color = this.getBackgroundImageColor();        			
        		}        		
        		imagePixels[i++] = color; 
        	}
        	if ((changePixelRange == true)&&(r > pixelEnd))
        	{
        		count++;
        		pixelStart = pixelEnd + (this.getCategorySpacePercent() * imageHeight) - this.getBarOffsetPixels();
        		pixelEnd = pixelStart + ((this.getBarWidthPercent() * this.getTotalDataset().getRowCount()) + (this.getBarSpacePercent() * (this.getTotalDataset().getRowCount() - 1))) * imageHeight + this.getBarOffsetPixels();   
        		changePixelRange = false;        		
        	}
        }
        Container container = new Container();
        image = container.createImage(new MemoryImageSource(imageWidth, imageHeight, imagePixels, 0, imageWidth));
        return image;			
	}
        
    private int getScrollMaximumValue()
    {
    	int value = 0;
    	if (this.getTotalDataset() != null)
    	{
    		value = this.getTotalDataset().getColumnCount();
    	}    	
    	return value;
    }
    
    private int getScrollMinimumValue()
    {
    	return 0;
    }    

	public void mouseWheelScrolled(MouseWheelEvent event) 
	{
		int value = 0;		
		if (event.getWheelRotation() < 0)
		{
			value = -1;
		}
		else
		{
			value = 1;
		}
		value += this.getNumberOfCategoriesSkipped();
		if ((this.getTotalDataset() != null)&&(value <= this.getTotalDataset().getColumnCount() - this.getNumberOfCategoriesDisplayed()))
		{			
			this.scrollBar.setValue(value);
		}				
	}

	public void scrollBarValueChanged(AdjustmentEvent event) 
	{
		this.setNumberOfCategoriesSkipped(event.getValue());
		this.setDisplayedDataset();						
	}		
}
ScrollableChartPane class...

Code: Select all

public class ScrollableChartPane extends JPanel implements AdjustmentListener, MouseWheelListener
{	
	private static final long serialVersionUID = 4126132915024047763L;
	private Box chartPaneBox = null;
	private ChartPanel chartPanel = null;
	private JScrollBar scrollBar = null;

	public ScrollableChartPane(ChartPanel chartPanel) throws SQLException
	{	
		super(new GridBagLayout());
		this.chartPanel = chartPanel;
		
		GridBagConstraints constraints = new GridBagConstraints();
		constraints = getConstraints(0, 0, 1, 1, GridBagConstraints.NONE, 0, 0, new Insets(0, 0, 0, 0), GridBagConstraints.CENTER, 0, 0);
		this.add(getChartBox(), constraints);
		
		this.setBorder(BorderFactory.createCompoundBorder
		(
			BorderFactory.createEmptyBorder(4, 4, 4, 4),
			BorderFactory.createEtchedBorder()
		));
		
		Plot plot = chartPanel.getChart().getPlot();
		if (plot instanceof ScrollableCategoryPlot)
		{
			((ScrollableCategoryPlot)plot).setScrollBar(this.getScrollBar());
		}		
		this.chartPanel.addMouseWheelListener(this);
	}
	
	private GridBagConstraints getConstraints(int gridx, int gridy, int gridwidth, int gridheight, int fill, int ipadx, int ipady, Insets insets, int anchor, int weightx, int weighty)
	{
		GridBagConstraints constraints = new GridBagConstraints();
		constraints.gridx = gridx;
		constraints.gridy = gridy;
		constraints.gridwidth = gridwidth;
		constraints.gridheight = gridheight;
		constraints.fill = fill;
		constraints.ipadx = ipadx;
		constraints.ipady = ipady;
		constraints.insets = insets;
		constraints.anchor = anchor;
		constraints.weightx = weightx;
		constraints.weighty = weighty;
		return constraints;		
	}
	
	private Box getChartBox() throws SQLException
	{
		if (chartPaneBox == null)
		{
			chartPaneBox = Box.createHorizontalBox();
			chartPaneBox.add(getChartPanel());			
			chartPaneBox.add(getScrollBar());
			chartPaneBox.setAlignmentX(Component.LEFT_ALIGNMENT);
			chartPaneBox.setAlignmentY(Component.TOP_ALIGNMENT);
		}
		return chartPaneBox;
	}
	
	private ChartPanel getChartPanel()
	{
		chartPanel.setMaximumSize(new Dimension(650, 450));
		chartPanel.setMinimumSize(new Dimension(650, 450));	
		return chartPanel;
	}
	
	public JScrollBar getScrollBar()
	{
		if (scrollBar == null)
		{
			scrollBar = new JScrollBar(SwingConstants.VERTICAL);
			scrollBar.setMinimum(0);
			scrollBar.setMaximum(100);			
            scrollBar.addAdjustmentListener(this);
            scrollBar.addMouseWheelListener(this);
		}
		return scrollBar;
	}

	public void adjustmentValueChanged(AdjustmentEvent event) 
	{
		Plot plot = null;		
		if ((this.chartPanel != null)&&(this.chartPanel.getChart() != null)&&(this.chartPanel.getChart().getPlot() != null))
		{
			plot = this.chartPanel.getChart().getPlot();
			if (plot instanceof ScrollableCategoryPlot)
			{
				((ScrollableCategoryPlot)plot).scrollBarValueChanged(event);				
			}
		}		
	}

	public void mouseWheelMoved(MouseWheelEvent event) 
	{
		Plot plot = null;		
		if ((this.chartPanel != null)&&(this.chartPanel.getChart() != null)&&(this.chartPanel.getChart().getPlot() != null))
		{
			plot = this.chartPanel.getChart().getPlot();
			if (plot instanceof ScrollableCategoryPlot)
			{
				((ScrollableCategoryPlot)plot).mouseWheelScrolled(event);				
			}
		}				
	}	
}
And last but not least a working example...

Code: Select all

public class TestCode extends JFrame
{	
	private static final long serialVersionUID = 1L;

    public TestCode()
    {
    	super();
    	CategoryAxis3D yAxis = new CategoryAxis3D("Y Axis Title");
		ValueAxis xAxis = new NumberAxis3D("X Axis Title");
		BarRenderer3D renderer = new BarRenderer3D();			
				
		ScrollableCategoryPlot categoryPlot = new ScrollableCategoryPlot(new DefaultCategoryDataset(), yAxis, xAxis, renderer);
		JFreeChart chart = new JFreeChart("Chart Title", JFreeChart.DEFAULT_TITLE_FONT, categoryPlot, true);
		ChartPanel chartPanel = new ChartPanel(chart);		
		
		try 
		{
			ScrollableChartPane scrollableChartPane = new ScrollableChartPane(chartPanel);
			this.setContentPane(scrollableChartPane);
			setSize(710, 540);
			setVisible(true);		
			setResizable(false);		
			addWindowListener(new CloseListener());
			
			//set the dataset at a later time...
			DefaultCategoryDataset dataset = new DefaultCategoryDataset();
			categoryPlot.setNumberOfCategoriesDisplayed(3);
			dataset.addValue(32, "Series1", "Category1");
			dataset.addValue(8, "Series2", "Category1");
			dataset.addValue(14, "Series3", "Category1");
			dataset.addValue(9, "Series1", "Category2");
			dataset.addValue(24, "Series2", "Category2");
			dataset.addValue(15, "Series3", "Category2");
			dataset.addValue(30, "Series1", "Category3");
			dataset.addValue(7, "Series2", "Category3");
			dataset.addValue(26, "Series3", "Category3");
			dataset.addValue(19, "Series1", "Category4");
			dataset.addValue(10, "Series2", "Category4");
			dataset.addValue(21, "Series3", "Category4");
			dataset.addValue(12, "Series1", "Category5");
			dataset.addValue(31, "Series2", "Category5");
			dataset.addValue(12, "Series3", "Category5");
			dataset.addValue(5, "Series1", "Category6");
			dataset.addValue(6, "Series2", "Category6");
			dataset.addValue(14, "Series3", "Category6");
			categoryPlot.setTotalDataset(dataset);
		} 
		catch (SQLException e) 
		{			
			e.printStackTrace();
		}
    }
    
    private class CloseListener extends WindowAdapter
	{
		public void windowClosing(WindowEvent event)
		{			
			dispose();
			System.exit(0);
		}		
	}
	
	public static void main(String[] args)
	{
		new TestCode();		
	}
} 
There it is and enjoy.
Thanks moonblade for the code on using a JSlider with the XYPlot class.

Locked