JFreeChart PolarPlot: mathematical orientation

I'd like to create a polar plot where the data is plotted in mathematical orientation (thus, the series starts and the east and continues counter-clockwise). The default behavior of JFreeChart's PolarPlot is to start north and continue the series clockwise.

Is there any support for this built in the PolarPlot class? I know how to transform the data to reach the goal, but this approach is rather cumbersome, since I'd need to adapt the angle labeling too.


As an aside, org.jfree.chart.plot.PolarPlot appears to have been designed for navigational and geodetic applications.

Using the transformation θ' = π/4 – θ and overriding refreshAngleTicks(), as suggested by @mort, produces reasonable results.

Addendum: See also this variation using the new PolarPlot API.

archimedes spiral

import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTick;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PolarPlot;
import org.jfree.chart.renderer.DefaultPolarItemRenderer;
import org.jfree.chart.renderer.PolarItemRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.TextAnchor;

/**
 * @see http://en.wikipedia.org/wiki/Polar_coordinate_system
 * @see https://stackoverflow.com/questions/3458824
 */
public class ArchimedesSpiral extends JFrame {

    private static final String title = "Archimedes' Spiral";

    public ArchimedesSpiral(String title) {
        super(title);
        JFreeChart chart = createChart(createDataset());
        ChartPanel panel = new ChartPanel(chart);
        panel.setPreferredSize(new Dimension(500, 500));
        panel.setMouseZoomable(false);
        this.add(panel);
    }

    private static XYDataset createDataset() {
        XYSeriesCollection result = new XYSeriesCollection();
        XYSeries series = new XYSeries(title);
        for (int t = 0; t <= 3 * 360; t++) {
            series.add(90 - t, t);
        }
        result.addSeries(series);
        return result;
    }

    private static JFreeChart createChart(XYDataset dataset) {
        ValueAxis radiusAxis = new NumberAxis();
        radiusAxis.setTickLabelsVisible(false);
        PolarItemRenderer renderer = new DefaultPolarItemRenderer();
        PolarPlot plot = new PolarPlot(dataset, radiusAxis, renderer) {

            @Override
            protected List refreshAngleTicks() {
                List<NumberTick> ticks = new ArrayList<NumberTick>();
                int delta = (int) this.getAngleTickUnit().getSize();
                for (int t = 0; t < 360; t += delta) {
                    int tp = (360 + 90 - t) % 360;
                    NumberTick tick = new NumberTick(
                        Double.valueOf(t), String.valueOf(tp),
                        TextAnchor.CENTER, TextAnchor.CENTER, 0.0);
                    ticks.add(tick);
                }
                return ticks;
            }
        };
        plot.setBackgroundPaint(new Color(0x00f0f0f0));
        plot.setRadiusGridlinePaint(Color.gray);
        plot.addCornerTextItem("r(θ) = θ; 0 < θ < 6π");
        JFreeChart chart = new JFreeChart(
            title, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
        chart.setBackgroundPaint(Color.white);
        return chart;
    }

    public static void main(String[] args) {
        ArchimedesSpiral demo = new ArchimedesSpiral(title);
        demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        demo.pack();
        demo.setLocationRelativeTo(null);
        demo.setVisible(true);
    }
}

The current version of JFreeChart seems to solve this issue a lot easier: There are three methods available:

setCounterClockwise(true) // changes the direction of the ticks
setAxisLocation(PolarAxisLocation.EAST_BELOW) // defines the placement of the axis
setAngleOffset(0);

Complete example adapted from here:

import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JFrame;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PolarAxisLocation;
import org.jfree.chart.plot.PolarPlot;
import org.jfree.chart.renderer.DefaultPolarItemRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/**
 * @see http://en.wikipedia.org/wiki/Polar_coordinate_system
 * @see https://stackoverflow.com/questions/3458824
 * @see https://stackoverflow.com/questions/6540390
 * @see https://stackoverflow.com/questions/6576911
 * @see https://stackoverflow.com/a/10227275/230513
 */
public class ArchimedesSpiral extends JFrame {

    private static final String title = "Archimedes' Spiral";

    public ArchimedesSpiral(String title) {
        super(title);
        JFreeChart chart = createChart(createDataset());
        ChartPanel panel = new ChartPanel(chart);
        panel.setPreferredSize(new Dimension(500, 500));
        panel.setMouseZoomable(false);
        this.add(panel);
    }

    private static XYDataset createDataset() {
        XYSeriesCollection result = new XYSeriesCollection();
        XYSeries series = new XYSeries(title);
        for (int t = 0; t <= 3 * 360; t++) {
            series.add(t, t);
        }
        result.addSeries(series);
        return result;
    }

    private static JFreeChart createChart(XYDataset dataset) {
        ValueAxis radiusAxis = new NumberAxis();
        radiusAxis.setTickLabelsVisible(false);
        DefaultPolarItemRenderer renderer = new DefaultPolarItemRenderer();
        renderer.setShapesVisible(false);
        PolarPlot plot = new PolarPlot(dataset, radiusAxis, renderer);
        plot.setCounterClockwise(true);
        plot.setAxisLocation(PolarAxisLocation.EAST_BELOW);
        plot.setAngleOffset(0);
        plot.setBackgroundPaint(new Color(0x00f0f0f0));
        plot.setRadiusGridlinePaint(Color.gray);
        plot.addCornerTextItem("r(θ) = θ; 0 < θ < 6π");
        JFreeChart chart = new JFreeChart(
            title, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
        chart.setBackgroundPaint(Color.white);
        return chart;
    }

    public static void main(String[] args) {
        ArchimedesSpiral demo = new ArchimedesSpiral(title);
        demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        demo.pack();
        demo.setLocationRelativeTo(null);
        demo.setVisible(true);
    }
}

Unfortunately, there seems to be no built-in support for this. The angle labeling can be adapted by overriding the refreshAngleTicks() methods of PolarPlot:

PolarPlot plot = new PolarPlot() {

        @Override
        protected List refreshAngleTicks() {
            List ticks = new ArrayList();
            // produce some ticks, e.g. NumberTick instances
            ticks.add(new NumberTick(0, "90", TextAnchor.TOP_LEFT, TextAnchor.TOP_LEFT, 0));
            ticks.add(new NumberTick(45, "45", TextAnchor.TOP_LEFT, TextAnchor.TOP_LEFT, 0));
            ticks.add(new NumberTick(90, "0", TextAnchor.TOP_LEFT, TextAnchor.TOP_LEFT, 0));
            ticks.add(new NumberTick(135, "315", TextAnchor.TOP_LEFT, TextAnchor.TOP_LEFT, 0));
            ticks.add(new NumberTick(180, "270", TextAnchor.TOP_LEFT, TextAnchor.TOP_LEFT, 0));
            ticks.add(new NumberTick(225, "225", TextAnchor.TOP_LEFT, TextAnchor.TOP_LEFT, 0));
            ticks.add(new NumberTick(270, "180", TextAnchor.TOP_LEFT, TextAnchor.TOP_LEFT, 0));
            ticks.add(new NumberTick(315, "135", TextAnchor.TOP_LEFT, TextAnchor.TOP_LEFT, 0));
            return ticks;
        }
    };