new chart type "geographical chart"

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
matinh
Posts: 483
Joined: Fri Aug 11, 2006 10:08 am
Location: Austria

new chart type "geographical chart"

Post by matinh » Mon Jan 08, 2007 10:49 am

Hi all!

I'm working on a new chart type and want to get some ideas from others. Let me first explain what the new chart type looks like.

It's a "geographical chart" with a map as it's background. Each data item (which might be some air-pollution-value meassured at some location) is represented by a colored dot (e.g, green for lower values and red for high/critical values). With each data item associated is a geographical location on the map. Such a chart could look like this:
Image

What I have so far is:
- a GeoPlot class
- a GeoAxis class
- a GeoDataset interface
- a ValueColorMapper interface
- a GeoColoredShapeRenderer class

The GeoPlot is responsible for drawing the map and calling the draw() methods of the axes and renderer.

The GeoAxis class basically converts geographical coordinates to pixels on the Rectangle2D area. Therefore it has to know about the minimal and maximal geographical coordinates of the background map.

The GeoDataset interface extends the SeriesDataset and provides methods for querying the value and the geographical position of a data item. The geographical position is provided as latitude and longitude (see http://en.wikipedia.org/wiki/Geographic ... ate_system).

The ValueColorMapper interface provides a method which returns a color for a given value. Thus a classification of values to "ok", "bad", and "critical" can easily be done by creating a new ValueColorMapper.

And finally the GeoColoredShapeRenderer class renders the dataset by drawing a shape (currently a dot) with the appropriate color on the appropriate position.

What do you think about the new chart. Would it be useful for others as well?

And as this is my first coding in the JFreeChart project any suggestions/critics/whatever is very welcome!

cheers,
- martin

angel
Posts: 899
Joined: Thu Jan 15, 2004 12:07 am
Location: Germany - Palatinate

Post by angel » Tue Jan 09, 2007 10:14 am

I don't want to discourage you but a while ago I also was making an application with "geographical charts". My first intention was using JFreeChart because of the good experiences and I thought it would fit good inside. But I've learned very fast that the geographical references are a very complex field. For example, the earth is a globe and you need complex projections to transform it into 2D.

So I used GeoTools (http://geotools.codehaus.org/) which also open source.

matinh
Posts: 483
Joined: Fri Aug 11, 2006 10:08 am
Location: Austria

Post by matinh » Tue Jan 09, 2007 11:00 am

Hi angle!

Even if I'm not familiar with GIS by now I agree with you that it is a very complex field. However, for small enough maps a linear approximation should do (at least for my needs). For example if I have a map of an 100km x 100km area and assume the earth is a flat cylinder (or is it rectangular ;-)), the exact points may be dislocated by at most a few pixels.

If one needs an exact representation he could create a new, more complex GeoAxis subclass which performs the necessary calculations.

Anyways, I'll have a look at GeoTools.

Tanks again,
- martin

matinh
Posts: 483
Joined: Fri Aug 11, 2006 10:08 am
Location: Austria

first version of geocharts available

Post by matinh » Wed Jan 17, 2007 6:00 pm

Hi all!

I added support for a new chart type geochart to JFreeChart. A first version is now available for download at
http://www.xss.co.at/~martin/jfreechart/.

To test it unpack it, compile it and run the demo as followilng (under linux):

Code: Select all

tar xzvf jfree-geo-0.1-src.tar.gz
cd jfree-geo-0.1/
mvn compile
cd target/classes/
java -cp .:[path-to-jfree-jar]/jfreechart-1.0.3.jar:[path-to-jcommon-jar]/jcommon-1.0.6.jar at.co.xss.jfree.demo.GeoChartDemo
This first version is by far not complete but you hopefully get the idea what it's all about. And if others could help with some code or ideas I would be more than happy to see this new chart type supported by one of the next versions of JFreeChart. Of course only with David's consent.

Comments?

- martin
Last edited by matinh on Mon Oct 22, 2007 12:20 pm, edited 1 time in total.

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 Jan 17, 2007 6:31 pm

Hi Martin,

This looks pretty interesting...I'll try to look at your code tomorrow.
David Gilbert
JFreeChart Project Leader

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

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 » Fri Jan 19, 2007 12:37 pm

Hi Martin,

I had a scan over your code and ran the demo. The map image isn't in the archive, as far as I can see, so the background was blank, but I get the idea anyway. You've done a good job on this.

One thing I'm wondering - would it make sense to use the "Decimal Degree" convention mentioned in the Wikipedia page, for the coordinates. If so, the datasets could be represented through the XYDataset interface (because the coordinates are just numbers) and the plotting could be done on an XYPlot. The XYBlockRenderer class (still in the 'experimental' directory) could be used for the renderer. The upside of doing this would be that all the existing XYPlot code is reused, and there's less maintenance. The downside is that the existing framework might constrain the geo plotting somewhere (I'm not familiar enough with geo plotting to know where the constraints might arise).
David Gilbert
JFreeChart Project Leader

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

matinh
Posts: 483
Joined: Fri Aug 11, 2006 10:08 am
Location: Austria

Post by matinh » Fri Jan 19, 2007 2:11 pm

Hi!
I had a scan over your code and ran the demo. The map image isn't in the archive, as far as I can see, so the background was blank, but I get the idea anyway.
Oops, you're right.
You've done a good job on this.
Thanks.
One thing I'm wondering - would it make sense to use the "Decimal Degree" convention mentioned in the Wikipedia page, for the coordinates. If so, the datasets could be represented through the XYDataset interface (because the coordinates are just numbers) and the plotting could be done on an XYPlot.
Hm... how would you get the coordinates then? You need the longitude, the latitude and the actual value, which are 3 dimensions. I don't see how the XYDataset could help here.
Maybe the XYZDataset could be used instead?

If we could reuse the existing XY[Z]Dataset and XYPlot code that would be really cool, but I'm not sure how/if we can do this. Could you please explain this in more details to me.
The downside is that the existing framework might constrain the geo plotting somewhere (I'm not familiar enough with geo plotting to know where the constraints might arise)
I'm not (now) familiar with geo plotting either :-( Anyway, reusing existing code is a good idea anyway.

If I'll find some time during the weekend I'll look into this.

- martin

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 » Fri Jan 19, 2007 2:36 pm

Sorry, you are right. XYZDataset would be needed, not XYDataset. If you get a chance, take a look at the XYBlockRenderer in the 'experimental' source tree (included in the distribution). There are a few demos also.
David Gilbert
JFreeChart Project Leader

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

matinh
Posts: 483
Joined: Fri Aug 11, 2006 10:08 am
Location: Austria

Post by matinh » Mon Jan 22, 2007 11:25 am

Hi!

I was too busy during the weekend for checking out the XYBlockRenderer but I updated the demo to include the image. Use the new version 0.1.1 available at http://www.xss.co.at/~martin/jfreechart/.

I will look into the experimental source tree ASAP.

- martin
Last edited by matinh on Mon Oct 22, 2007 12:21 pm, edited 1 time in total.

matinh
Posts: 483
Joined: Fri Aug 11, 2006 10:08 am
Location: Austria

Post by matinh » Fri Jan 26, 2007 11:45 am

Hi!

Finally I found some time to take a closer look at XYBlockRenderer as you suggested and came to the following conclusion:

1) Using the XYZDataset instead of the New GeoDataset makes perfectly sense. This makes some of my new classes obsolete.

2) Using the XYBlockRenderer is an option but it has some features I don't like. For instance it can only draw blocks instead of any kind of shape. And: it scales the blocks with the axis which I really don't like. I would prefer to have a fixed size shape instead. However, implementing my own renderer shouldn't be too hard (I've already done most of the work in my last attempt).

3) The XYPlot + NumberAxis are fine for small enough maps as calculation of the graphics-device coordinates is nearly linear for them. However, the earth is not flat and thus calculation of GD-coordinates will not linear. Unfortunately I don't know much about this field. But we can leave this for future releases.

Another issue with the XYPlot is that it supports zooming. Zooming is a cool feature but in geographical charts it's not without zooming the background picture as well! Is there some method which allows for this? Or can zooming be disabled in the XYPlot? I didn't find appropriate methods.

4) Do you think it would make sense to add methods to the PaintScale interface to allow querying the min and the max value? This would be helpful for drawing a ScaleTitle. And BTW: Why don't you use a Range instead of min/max in GrayPaintScale for instance?

What do you mean?

- martin

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 » Fri Jan 26, 2007 5:00 pm

Thanks for the feedback...
matinh wrote:1) Using the XYZDataset instead of the New GeoDataset makes perfectly sense. This makes some of my new classes obsolete.
Great!
matinh wrote:2) Using the XYBlockRenderer is an option but it has some features I don't like. For instance it can only draw blocks instead of any kind of shape. And: it scales the blocks with the axis which I really don't like. I would prefer to have a fixed size shape instead. However, implementing my own renderer shouldn't be too hard (I've already done most of the work in my last attempt).
There's no problem to add more renderers in there. XYBlockRenderer is just something that happened to work for a couple of charts I needed.
matinh wrote:3) The XYPlot + NumberAxis are fine for small enough maps as calculation of the graphics-device coordinates is nearly linear for them. However, the earth is not flat and thus calculation of GD-coordinates will not linear. Unfortunately I don't know much about this field. But we can leave this for future releases.
I think the non-linear cases should most likely be left to specialised packages...I think that covering the simple map cases would be enough for JFreeChart.
matinh wrote:Another issue with the XYPlot is that it supports zooming. Zooming is a cool feature but in geographical charts it's not without zooming the background picture as well! Is there some method which allows for this? Or can zooming be disabled in the XYPlot? I didn't find appropriate methods.
I will try to implement an image annotation that scales the image area to follow the axes, so that zooming works for the image too. That isn't too hard to do.
matinh wrote:4) Do you think it would make sense to add methods to the PaintScale interface to allow querying the min and the max value? This would be helpful for drawing a ScaleTitle. And BTW: Why don't you use a Range instead of min/max in GrayPaintScale for instance?
Yes, I think it does make sense. Are you working on a ScaleTitle - note that I committed a PaintScaleLegend to CVS the other day, which displays the range of colors, and updated the demos to incorporate it.

For GrayPaintScale, I don't think it matters too much whether we use min/max or Range...it is straightforward either way.
David Gilbert
JFreeChart Project Leader

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

krheinwald
Posts: 15
Joined: Thu Mar 27, 2003 10:06 am

Post by krheinwald » Fri Jan 26, 2007 6:29 pm

Martin, David

this is most interesting! I am actually currently looking for ways to display a route on a map and this might actually be the solution!

Two questions:

- Would it be possible to draw a XY-LineChart on the map? I am thinking of having the coordinates in a XY-Dataset, no Z-Value needed.

- How would new maps be added and registered, both from a developer as well as from a user standpoint?

Thanks,
Klaus

matinh
Posts: 483
Joined: Fri Aug 11, 2006 10:08 am
Location: Austria

Post by matinh » Sat Jan 27, 2007 12:31 am

I think the non-linear cases should most likely be left to specialised packages...I think that covering the simple map cases would be enough for JFreeChart.
Whatever you say ;-)
I will try to implement an image annotation that scales the image area to follow the axes, so that zooming works for the image too. That isn't too hard to do.
That would be great! Let me know when this is available.
Are you working on a ScaleTitle - note that I committed a PaintScaleLegend to CVS the other day, which displays the range of colors, and updated the demos to incorporate it.
I'm not working on it right now. I had something in the sources I posted some time ago but if you have it already that would be fine. I didn't find it in CVS, could you please point me to the class.

Regarding Klaus' questions: drawing a line chart would be possible if you use/write a corresponding renderer. But you could just do this with an existing XYLineChart, loading a map as a background image and configure the axis to match the geographical coordinates of the map.
How would new maps be added and registered, both from a developer as well as from a user standpoint?
Registering a map wouldn't be anything else than simply loading a background image and specifying it's top/left and bottom/right coordinates.

- martin

matinh
Posts: 483
Joined: Fri Aug 11, 2006 10:08 am
Location: Austria

Post by matinh » Tue Mar 06, 2007 11:09 am

Hi David! Hi all!

I was quite busy recently, but now I had a closer look at the experimental classes and the XYBlockRenderer you recommended. It's a cool new renderer but not exactly what I needed. I didn't want an ugly block and I didn't want it to scale with the chart. So I created a new XYShapeRenderer class which can render any kind of shape (use setShape() to set a shape). The z-value changes the color of the shape according to the new PaintScale class. Here is the code:

Code: Select all

public class XYShapeRenderer
    extends AbstractXYItemRenderer 
    implements XYItemRenderer, Cloneable, Serializable
{

    /** The default shape used for rendering items. */
    private final static Shape DEFAULT_SHAPE = new Ellipse2D.Double(0, 0, 10, 10);

    /** The paint scale. */
    private PaintScale paintScale;
    
    
    /**
     * Creates a new <code>XYShapeRenderer</code> instance with default 
     * attributes.
     */
    public XYShapeRenderer() {
        this.paintScale = new LookupPaintScale();
        setShape(DEFAULT_SHAPE);
    }
    
    /**
     * Returns the paint scale used by the renderer.
     * 
     * @return The paint scale (never <code>null</code>).
     * 
     * @see #setPaintScale(PaintScale)
     * @since 1.0.4
     */
    public PaintScale getPaintScale() {
        return this.paintScale;
    }
    
    /**
     * Sets the paint scale used by the renderer.
     * 
     * @param scale  the scale (<code>null</code> not permitted).
     * 
     * @see #getPaintScale()
     * @since 1.0.4
     */
    public void setPaintScale(PaintScale scale) {
        if (scale == null) {
            throw new IllegalArgumentException("Null 'scale' argument.");
        }
        this.paintScale = scale;
        notifyListeners(new RendererChangeEvent(this));
    }
    
     /**
     * Draws the block representing the specified item.
     * 
     * @param g2  the graphics device.
     * @param state  the state.
     * @param dataArea  the data area.
     * @param info  the plot rendering info.
     * @param plot  the plot.
     * @param domainAxis  the x-axis.
     * @param rangeAxis  the y-axis.
     * @param dataset  the dataset.
     * @param series  the series index.
     * @param item  the item index.
     * @param crosshairState  the crosshair state.
     * @param pass  the pass index.
     */
    public void drawItem(Graphics2D g2, XYItemRendererState state, 
            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 
            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 
            int series, int item, CrosshairState crosshairState, int pass)
    {
        Shape entityArea = null;
        EntityCollection entities = null;
        if (info != null) {
            entities = info.getOwner().getEntityCollection();
        }

        double x = dataset.getXValue(series, item);
        double y = dataset.getYValue(series, item);
        double z = 0.0;
        if (dataset instanceof XYZDataset) {
            z = ((XYZDataset) dataset).getZValue(series, item);
        }
        Paint p = this.paintScale.getPaint(z);

        Double xx0 = domainAxis.valueToJava2D(x, dataArea, plot.getDomainAxisEdge());
        Double yy0 = rangeAxis.valueToJava2D(y, dataArea, plot.getRangeAxisEdge());

        Rectangle2D bounds = getSeriesShape(series).getBounds2D();
        Shape drawShape = ShapeUtilities.createTranslatedShape(getSeriesShape(series), xx0 - bounds.getWidth() / 2, yy0 - bounds.getHeight() / 2);
        if (plot.getOrientation().equals(PlotOrientation.HORIZONTAL))
            drawShape = ShapeUtilities.rotateShape(drawShape, Math.PI / 2, xx0.floatValue(), yy0.floatValue());
        entityArea = drawShape;

        g2.setPaint(p);
        g2.fill(drawShape);

        g2.setStroke(new BasicStroke());
        g2.setPaint(Color.gray);
        g2.draw(drawShape);

        // add an entity for the item...
        if (entities != null) {
            addEntity(entities, entityArea, dataset, series, item, xx0, yy0);
        }
    }
    
    /**
     * Tests this <code>XYBlockRenderer</code> for equality with an arbitrary
     * object.  This method returns <code>true</code> if and only if:
     * <ul>
     * <li><code>obj</code> is an instance of <code>XYBlockRenderer</code> (not
     *     <code>null</code>);</li>
     * <li><code>obj</code> has the same field values as this 
     *     <code>XYBlockRenderer</code>;</li>
     * </ul>
     * 
     * @param obj  the object (<code>null</code> permitted).
     * 
     * @return A boolean.
     */
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof XYShapeRenderer)) {
            return false;
        }
        XYShapeRenderer that = (XYShapeRenderer) obj;
        if (!this.paintScale.equals(that.paintScale)) {
            return false;
        }
        return super.equals(obj);
    }
    
    /**
     * Returns a clone of this renderer.
     * 
     * @return A clone of this renderer.
     * 
     * @throws CloneNotSupportedException if there is a problem creating the 
     *     clone.
     */
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
What do you mean? Chances to get it included in 1.0.5?

About the background image zooming we talked some time ago: did you find some time to implement the zooming you mentioned? This would be really nice (and a must for the geographical charts).


comments are welcome,
- martin

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 Mar 08, 2007 4:59 pm

I tried the renderer. By accident, I noticed that it doesn't work when the plot orientation is horizontal. Otherwise it seems fine. I'm not sure what I'd use it for, so if you could put together a realistic demo, that would be helpful.

I think there may be a problem in the clone() method in XYBlockRenderer (the paint scale needs to be cloned too) which you've carried across to the XYShapeRenderer. That's easy to fix though.

I think this can go into 1.0.5 if we fix up those little problems.
David Gilbert
JFreeChart Project Leader

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

Locked