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:  not-yet-released
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    
029    package org.jfree.beans;
030    
031    import java.awt.Font;
032    import java.awt.Insets;
033    import java.awt.Paint;
034    import java.awt.event.MouseEvent;
035    import java.beans.PropertyChangeEvent;
036    import java.beans.PropertyEditorManager;
037    
038    import javax.swing.event.EventListenerList;
039    
040    import org.jfree.beans.editors.RotationEditor;
041    import org.jfree.beans.events.SectionClickEvent;
042    import org.jfree.beans.events.SectionClickListener;
043    import org.jfree.chart.JFreeChart;
044    import org.jfree.chart.entity.ChartEntity;
045    import org.jfree.chart.entity.EntityCollection;
046    import org.jfree.chart.entity.PieSectionEntity;
047    import org.jfree.chart.labels.PieSectionLabelGenerator;
048    import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
049    import org.jfree.chart.labels.StandardPieToolTipGenerator;
050    import org.jfree.chart.plot.PiePlot;
051    import org.jfree.data.general.DefaultPieDataset;
052    import org.jfree.data.general.PieDataset;
053    import org.jfree.util.Rotation;
054    
055    /**
056     * A JavaBean that displays a pie chart.
057     */
058    public 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    }