001/* ======================================================
002 * Orson : a free chart beans library based on JFreeChart
003 * ======================================================
004 *
005 * (C) Copyright 2007, by Object Refinery Limited.
006 *
007 * Project Info:  http://www.jfree.org/orson/
008 *
009 * This library is free software; you can redistribute it and/or modify it 
010 * under the terms of the GNU Lesser General Public License as published by 
011 * the Free Software Foundation; either version 2.1 of the License, or 
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but 
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022 * USA.  
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025 * in the United States and other countries.]
026 * 
027 */
028
029package org.jfree.beans;
030
031import java.awt.Font;
032import java.awt.Insets;
033import java.awt.Paint;
034import java.awt.event.MouseEvent;
035import java.beans.PropertyChangeEvent;
036import java.beans.PropertyEditorManager;
037
038import javax.swing.event.EventListenerList;
039
040import org.jfree.beans.editors.RotationEditor;
041import org.jfree.beans.events.SectionClickEvent;
042import org.jfree.beans.events.SectionClickListener;
043import org.jfree.chart.JFreeChart;
044import org.jfree.chart.entity.ChartEntity;
045import org.jfree.chart.entity.EntityCollection;
046import org.jfree.chart.entity.PieSectionEntity;
047import org.jfree.chart.labels.PieSectionLabelGenerator;
048import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
049import org.jfree.chart.labels.StandardPieToolTipGenerator;
050import org.jfree.chart.plot.PiePlot;
051import org.jfree.data.general.DefaultPieDataset;
052import org.jfree.data.general.PieDataset;
053import org.jfree.util.Rotation;
054
055/**
056 * A JavaBean that displays a pie chart.
057 */
058public class JPieChart extends AbstractChart {
059
060    static {
061        PropertyEditorManager.registerEditor(Rotation.class, 
062                RotationEditor.class);
063    }
064    
065    /** The section label format string. */
066    private String labelFormat;
067    
068    /** Storage for registered sectionClickListeners. */
069    private EventListenerList sectionClickListeners;
070
071    /**
072     * Creates a new pie chart bean.
073     */
074    public JPieChart() {
075        super();
076        this.sectionClickListeners = new EventListenerList();
077        this.labelFormat = "{0}";
078    }
079    
080    /**
081     * Creates a default chart.
082     * 
083     * @return The default chart.
084     */
085    protected JFreeChart createDefaultChart() {
086        DefaultPieDataset dataset = new DefaultPieDataset();
087        dataset.setValue("A", 5.0);
088        dataset.setValue("B", 7.0);
089        dataset.setValue("C", 6.0);
090        PiePlot plot = new PiePlot(dataset);
091        JFreeChart chart = new JFreeChart(plot);
092        chart.setTitle("JPieChart - Title");
093        plot.setToolTipGenerator(new StandardPieToolTipGenerator());
094        return chart;
095    }
096    
097    /**
098     * Returns the direction (clockwise or anti-clockwise) in which the pie 
099     * segments are drawn.
100     * 
101     * @return The direction.
102     * 
103     * @see #setDirection(Rotation)
104     */
105    public Rotation getDirection() {
106        Rotation result = null;
107        PiePlot plot = (PiePlot) this.chart.getPlot();
108        if (plot != null) {
109            result = plot.getDirection();
110        }
111        return result;        
112    }
113    
114    /**
115     * Sets the direction in which the pie sections are drawn and fires a
116     * {@link PropertyChangeEvent} for the <code>direction</code> property.
117     * 
118     * @param direction  the new direction (<code>null</code> not permitted).
119     * 
120     * @see #getDirection()
121     */
122    public void setDirection(Rotation direction) {
123        PiePlot plot = (PiePlot) this.chart.getPlot();
124        if (plot != null) {
125            Rotation old = plot.getDirection();
126            plot.setDirection(direction);
127            firePropertyChange("direction", old, direction);
128        }        
129    }
130    
131    /**
132     * Returns the dataset used by the chart.
133     * 
134     * @return The dataset (possibly <code>null</code>).
135     * 
136     * @see #setDataset(PieDataset)
137     */
138    public PieDataset getDataset() {
139        PieDataset result = null;
140        PiePlot plot = (PiePlot) this.chart.getPlot();
141        if (plot != null) {
142            result = plot.getDataset();
143        }
144        return result;
145    }
146    
147    /**
148     * Sets the dataset used by the chart and fires a 
149     * {@link PropertyChangeEvent} for the <code>dataset</code> property.
150     * 
151     * @param dataset  the dataset (<code>null</code> permitted).
152     * 
153     * @see #getDataset()
154     */
155    public void setDataset(PieDataset dataset) {
156        PiePlot plot = (PiePlot) this.chart.getPlot();
157        if (plot != null) {
158            PieDataset old = plot.getDataset();
159            plot.setDataset(dataset);
160            firePropertyChange("dataset", old, dataset);
161        }
162    }
163    
164    /**
165     * Returns a flag that controls whether the plot is circular or
166     * elliptical.
167     * 
168     * @return A flag.
169     * 
170     * @see #setCircular(boolean)
171     */
172    public boolean isCircular() {
173        PiePlot plot = (PiePlot) this.chart.getPlot();
174        return plot.isCircular();
175    }
176    
177    /**
178     * Sets the flag that controls whether the pie chart is drawn as a circle
179     * or an ellipse and fires a {@link PropertyChangeEvent} for the 
180     * <code>circular</code> property.
181     * 
182     * @param circular  the flag.
183     * 
184     * @see #isCircular()
185     */
186    public void setCircular(boolean circular) {
187        PiePlot plot = (PiePlot) this.chart.getPlot();
188        boolean old = plot.isCircular();
189        plot.setCircular(circular);
190        firePropertyChange("circular", old, circular);
191    }
192    
193    /**
194     * Returns the angle from which the first pie section starts.
195     * 
196     * @return The angle.
197     * 
198     * @see #setPieStartingAngle(double)
199     */
200    public double getPieStartingAngle() {
201        PiePlot plot = (PiePlot) this.chart.getPlot();
202        return plot.getStartAngle();
203    }
204    
205    /**
206     * Sets the angle at which the first pie section starts and fires a 
207     * {@link PropertyChangeEvent} for the <code>pieStartingAngle</code> 
208     * property.
209     * 
210     * @param angle  the angle.
211     * 
212     * @see #getPieStartingAngle()
213     */
214    public void setPieStartingAngle(double angle) {
215        PiePlot plot = (PiePlot) this.chart.getPlot();
216        double old = plot.getStartAngle();
217        plot.setStartAngle(angle);
218        firePropertyChange("pieStartingAngle", old, angle);
219    }
220    
221    /**
222     * Returns the label format used by the plot.
223     * 
224     * @return The label format.
225     * 
226     * @see #setLabelFormat(String)
227     */
228    public String getLabelFormat() {
229        String result = null;
230        PiePlot plot = (PiePlot) this.chart.getPlot();
231        PieSectionLabelGenerator g = plot.getLabelGenerator();
232        if (g instanceof StandardPieSectionLabelGenerator) {
233            StandardPieSectionLabelGenerator gg 
234                    = (StandardPieSectionLabelGenerator) g;
235            result = gg.getLabelFormat();
236        }
237        return result;
238    }
239    
240    /**
241     * Returns the format string for the section labels and fires a 
242     * {@link PropertyChangeEvent} for the <code>labelFormat</code> property.
243     * 
244     * @param format  the format string.
245     * 
246     * @see #getLabelFormat()
247     */
248    public void setLabelFormat(String format) {
249        PiePlot plot = (PiePlot) this.chart.getPlot();
250        String old = this.labelFormat;
251        this.labelFormat = format;
252        plot.setLabelGenerator(new StandardPieSectionLabelGenerator(format));
253        firePropertyChange("labelFormat", old, format);
254    }
255    
256    /**
257     * Returns the font used to display the section labels.
258     * 
259     * @return The font.
260     * 
261     * @see #setLabelFont(Font)
262     */
263    public Font getLabelFont() {
264        Font result = null;
265        PiePlot plot = (PiePlot) this.chart.getPlot();
266        if (plot != null) {
267            return plot.getLabelFont();
268        }
269        return result;
270    }
271    
272    /**
273     * Sets the font used to draw the section labels and fires a 
274     * {@link PropertyChangeEvent} for the <code>labelFont</code> property.
275     * 
276     * @param font  the font.
277     * 
278     * @see #getLabelFont()
279     */
280    public void setLabelFont(Font font) {
281        PiePlot plot = (PiePlot) this.chart.getPlot();
282        if (plot != null) {
283            Font old = plot.getLabelFont();
284            plot.setLabelFont(font);
285            firePropertyChange("labelFont", old, font);
286        }
287    }
288    
289    /**
290     * Returns the paint used to draw the section labels.
291     * 
292     * @return The paint.
293     * 
294     * @see #setLabelPaint(Paint)
295     */
296    public Paint getLabelPaint() {
297        Paint result = null;
298        PiePlot plot = (PiePlot) this.chart.getPlot();
299        if (plot != null) {
300            return plot.getLabelPaint();
301        }
302        return result;
303    }
304    
305    /**
306     * Sets the paint used to draw the section labels and fires a 
307     * {@link PropertyChangeEvent} for the <code>labelPaint</code> property.
308     * 
309     * @param paint  the paint.
310     * 
311     * @see #getLabelPaint()
312     */
313    public void setLabelPaint(Paint paint) {
314        PiePlot plot = (PiePlot) this.chart.getPlot();
315        if (plot != null) {
316            Paint old = plot.getLabelPaint();
317            plot.setLabelPaint(paint);
318            firePropertyChange("labelPaint", old, paint);
319        }
320    }
321    
322    /**
323     * Returns the format string for the section tool tips.
324     * 
325     * @return The format string.
326     * 
327     * @see #setSectionToolTipFormat(String)
328     */
329    public String getSectionToolTipFormat() {
330        PiePlot p = (PiePlot) this.chart.getPlot();
331        if (p == null) {
332            return "";
333        }
334        StandardPieToolTipGenerator g = (StandardPieToolTipGenerator) 
335                p.getToolTipGenerator();
336        if (g == null) {
337            return "";
338        }
339        return g.getLabelFormat();
340    }
341    
342    /**
343     * Sets the format string for the section tool tips and fires a 
344     * {@link PropertyChangeEvent} for the <code>sectionToolTipFormat</code>.
345     * 
346     * @param format  the format string.
347     * 
348     * @see #getSectionToolTipFormat()
349     */
350    public void setSectionToolTipFormat(String format) {
351        PiePlot p = (PiePlot) this.chart.getPlot();
352        if (p == null) {
353            return;
354        }
355        if (format.equals("")) {
356            p.setToolTipGenerator(null);
357        }
358        else {
359            p.setToolTipGenerator(new StandardPieToolTipGenerator(format));   
360        } 
361        // FIXME: the old value needs to be specified
362        firePropertyChange("sectionToolTipFormat", "", format);
363    }
364    
365    /**
366     * Registers a listener to receive notification of section clicks.
367     * 
368     * @param listener  the listener (<code>null</code> not permitted).
369     */
370    public void addSectionClickListener(SectionClickListener listener) {
371        if (listener == null) {
372            throw new IllegalArgumentException("Null 'listener' argument.");
373        }
374        this.sectionClickListeners.add(SectionClickListener.class, listener);
375    }
376    
377    /**
378     * Unregisters a listener so that it no longer receives notification of 
379     * section clicks.
380     * 
381     * @param listener  the listener (<code>null</code> not permitted).
382     */
383    public void removeSectionClickListener(SectionClickListener listener) {
384        if (listener == null) {
385            throw new IllegalArgumentException("Null 'listener' argument.");
386        }
387        this.sectionClickListeners.remove(SectionClickListener.class, listener);        
388    }
389    
390    /**
391     * Fires a section click event.
392     * 
393     * @param event  the event.
394     */
395    public void fireSectionClickEvent(SectionClickEvent event) {
396        Object[] listeners = this.sectionClickListeners.getListeners(
397                SectionClickListener.class);
398        for (int i = listeners.length - 1; i >= 0; i -= 1) {
399            ((SectionClickListener) listeners[i]).onSectionClick(event);
400        }                
401        
402    }
403    
404    /**
405     * If the user clicks on the chart, see if that translates into an event
406     * that we report...
407     * 
408     * @param event  the event.
409     */
410    public void mouseClicked(MouseEvent event) {
411        // if no-one is listening, just return...
412        Object[] listeners = this.sectionClickListeners.getListeners(
413                SectionClickListener.class);
414        if (listeners.length == 0) {
415            super.mouseClicked(event);
416            return;
417        }
418
419        Insets insets = getInsets();
420        int x = event.getX() - insets.left;
421        int y = event.getY() - insets.top;
422
423        ChartEntity entity = null;
424        if (this.info != null) {
425            EntityCollection entities = this.info.getEntityCollection();
426            if (entities != null) {
427                entity = entities.getEntity(x, y);
428            }
429        }
430        if (entity instanceof PieSectionEntity) {
431            PieSectionEntity pse = (PieSectionEntity) entity;
432            SectionClickEvent sce = new SectionClickEvent(this, 
433                    pse.getSectionKey());
434            fireSectionClickEvent(sce);
435        }
436        else  {
437            super.mouseClicked(event);
438        }
439    }
440
441}