KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > krysalis > jcharts > nonAxisChart > PieChart2D


1 /***********************************************************************************************
2  * Copyright 2002 (C) Nathaniel G. Auvil. All Rights Reserved.
3  *
4  * Redistribution and use of this software and associated documentation ("Software"), with or
5  * without modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain copyright statements and notices.
8  * Redistributions must also contain a copy of this document.
9  *
10  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
11  * conditions and the following disclaimer in the documentation and/or other materials
12  * provided with the distribution.
13  *
14  * 3. The name "jCharts" or "Nathaniel G. Auvil" must not be used to endorse or promote
15  * products derived from this Software without prior written permission of Nathaniel G.
16  * Auvil. For written permission, please contact nathaniel_auvil@users.sourceforge.net
17  *
18  * 4. Products derived from this Software may not be called "jCharts" nor may "jCharts" appear
19  * in their names without prior written permission of Nathaniel G. Auvil. jCharts is a
20  * registered trademark of Nathaniel G. Auvil.
21  *
22  * 5. Due credit should be given to the jCharts Project (http://jcharts.sourceforge.net/).
23  *
24  * THIS SOFTWARE IS PROVIDED BY Nathaniel G. Auvil AND CONTRIBUTORS ``AS IS'' AND ANY
25  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
27  * jCharts OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,STRICT LIABILITY, OR TORT
31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
32  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
33  ************************************************************************************************/

34
35 package org.krysalis.jcharts.nonAxisChart;
36
37
38 import org.krysalis.jcharts.Chart;
39 import org.krysalis.jcharts.chartData.interfaces.IPieChartDataSet;
40 import org.krysalis.jcharts.chartData.processors.PieChartDataProcessor;
41 import org.krysalis.jcharts.imageMap.CircleMapArea;
42 import org.krysalis.jcharts.imageMap.ImageMap;
43 import org.krysalis.jcharts.imageMap.PolyMapArea;
44 import org.krysalis.jcharts.properties.ChartProperties;
45 import org.krysalis.jcharts.properties.LegendAreaProperties;
46 import org.krysalis.jcharts.properties.LegendProperties;
47 import org.krysalis.jcharts.properties.PieChart2DProperties;
48 import org.krysalis.jcharts.test.HTMLChartTestable;
49 import org.krysalis.jcharts.test.HTMLGenerator;
50 import org.krysalis.jcharts.types.PieLabelType;
51
52 import java.awt.*;
53 import java.awt.font.FontRenderContext JavaDoc;
54 import java.awt.geom.Arc2D JavaDoc;
55 import java.awt.geom.Line2D JavaDoc;
56
57
58 /*************************************************************************************
59  *
60  * @author Nathaniel Auvil
61  * @version $Id: PieChart2D.java,v 1.7 2003/05/31 18:17:32 nathaniel_auvil Exp $
62  ************************************************************************************/

63 public class PieChart2D extends Chart implements HTMLChartTestable
64 {
65     private float pieX;
66     private float pieY;
67     private float diameter;
68
69     private IPieChartDataSet iPieChartDataSet;
70     private PieChartDataProcessor pieChartDataProcessor;
71
72     private PieLabels pieLabels;
73
74
75     /************************************************************************************************
76      * Constructor
77      *
78      * @param iPieChartDataSet
79      * @param legendProperties
80      * @param chartProperties general chart properties
81      * @param pixelWidth
82      * @param pixelHeight
83      ************************************************************************************************/

84     public PieChart2D( IPieChartDataSet iPieChartDataSet,
85                              LegendProperties legendProperties,
86                              ChartProperties chartProperties,
87                              int pixelWidth,
88                              int pixelHeight )
89     {
90         super( legendProperties, chartProperties, pixelWidth, pixelHeight );
91         this.iPieChartDataSet = iPieChartDataSet;
92     }
93
94
95     /************************************************************************************************
96      * Draws the chart
97      *
98      ************************************************************************************************/

99     protected void renderChart()
100     {
101         PieChart2DProperties properties = (PieChart2DProperties) this.iPieChartDataSet.getChartTypeProperties();
102         FontRenderContext JavaDoc fontRenderContext = super.getGraphics2D().getFontRenderContext();
103
104         this.pieChartDataProcessor = new PieChartDataProcessor( this.iPieChartDataSet );
105         this.pieChartDataProcessor.processData();
106
107
108         //---cache calcs used more than once
109
float edgePaddingTimesTwo = super.getChartProperties().getEdgePadding() * 2;
110         float halfImageWidth = super.getImageWidth() / 2;
111         float halfImageHeight = super.getImageHeight() / 2;
112
113
114         //---render the TITLE. If no title, this will return zero.
115
float chartTitleHeightPlusPadding = super.renderChartTitle( this.iPieChartDataSet.getChartTitle(), fontRenderContext );
116
117
118         //---figure out what size is needed to hold all the components of the pie chart, then size the pie accordingly
119
float widthAvailable = super.getImageWidth() - edgePaddingTimesTwo;
120         float heightAvailable = super.getImageHeight() - edgePaddingTimesTwo;
121         heightAvailable -= chartTitleHeightPlusPadding;
122
123
124         //---take labels sizing into consideration if needed.
125
if( !properties.getPieLabelType().equals( PieLabelType.NO_LABELS ) )
126         {
127             this.pieLabels = new PieLabels( properties, this.iPieChartDataSet, fontRenderContext );
128
129             //---if there is only one item in pie, label will be below plot so width is not a concern
130
if( iPieChartDataSet.getNumberOfDataItems() != 1 )
131             {
132                 widthAvailable -= this.pieLabels.getWidestLabelTimesTwo();
133                 widthAvailable -= ( properties.getTickLength() * 2 );
134             }
135
136             heightAvailable -= this.pieLabels.getTallestLabelTimesTwo();
137             heightAvailable -= ( properties.getTickLength() * 2 );
138         }
139
140
141         //---if there is a legend...
142
if( this.getLegend() != null )
143         {
144             float legendX = 0f;
145             float legendY = 0f;
146
147             //---calculate all the legend rendering coordinates and positions.
148
this.getLegend().calculateDrawingValues( iPieChartDataSet );
149
150
151             //---adjust width and height based on the Legend size.
152
if( ( this.getLegend().getLegendProperties().getPlacement() == LegendAreaProperties.RIGHT )
153                 || ( this.getLegend().getLegendProperties().getPlacement() == LegendAreaProperties.LEFT ) )
154             {
155                 widthAvailable -= this.getLegend().getWidth();
156                 widthAvailable -= this.getLegend().getLegendProperties().getChartPadding();
157
158
159                 //---diameter of pie will be at least one pixel, even if the legend takes up the whole image.
160
//---this will keep the renderer from blowing up.
161
this.diameter = Math.max( widthAvailable, 1.0f );
162
163                 //---make sure we do not make the pie diameter taller than the image
164
this.diameter = Math.min( this.diameter, heightAvailable );
165
166
167                 //---calculate the entire width of everything to be drawn so can center everything
168
float plotWidth = this.diameter;
169                 plotWidth += this.getLegend().getWidth();
170                 plotWidth += this.getLegend().getLegendProperties().getChartPadding();
171                 if( this.pieLabels != null )
172                 {
173                     plotWidth += ( this.pieLabels.getWidestLabel() * 2 );
174                     plotWidth += ( properties.getTickLength() * 2 );
175                 }
176
177                 if( this.getLegend().getLegendProperties().getPlacement() == LegendAreaProperties.RIGHT )
178                 {
179                     //---pie's diameter may not fill image width as may be image height constrained.
180
this.pieX = halfImageWidth - ( plotWidth / 2 );
181
182                     if( this.pieLabels != null )
183                     {
184                         this.pieX += this.pieLabels.getWidestLabel();
185                         this.pieX += properties.getTickLength();
186                         legendX += this.pieLabels.getWidestLabel();
187                         legendX += properties.getTickLength();
188                     }
189
190                     //---position legend based on the pie position
191
legendX += this.pieX + this.diameter;
192                     legendX += this.getLegend().getLegendProperties().getChartPadding();
193                 }
194                 else
195                 {
196                     legendX = halfImageWidth - ( plotWidth / 2 );
197
198                     if( this.pieLabels != null )
199                     {
200                         this.pieX = legendX;
201                         this.pieX += this.getLegend().getWidth();
202                         this.pieX += this.getLegend().getLegendProperties().getChartPadding();
203                         this.pieX += this.pieLabels.getWidestLabel();
204                         this.pieX += properties.getTickLength();
205                     }
206                 }
207
208                 //---center the legend vertically
209
legendY = halfImageHeight - ( this.getLegend().getHeight() / 2 );
210
211                 //---center the pie vertically
212
this.pieY = halfImageHeight - ( this.diameter / 2 );
213             }
214             //---else the legend is either under or on top of the pie
215
else
216             {
217                 heightAvailable-= this.getLegend().getHeight();
218                 heightAvailable-= this.getLegend().getLegendProperties().getChartPadding();
219
220                 //---diameter of pie will be at least one pixel, even if the legend takes up the whole image.
221
//---this will keep the renderer from blowing up.
222
this.diameter = Math.max( heightAvailable, 1.0f );
223
224                 //---make sure we do not make the pie diameter wider than the image
225
this.diameter = Math.min( this.diameter, widthAvailable );
226
227
228                 if( this.getLegend().getLegendProperties().getPlacement() == LegendAreaProperties.BOTTOM )
229                 {
230                     this.pieY = super.getChartProperties().getEdgePadding();
231                     this.pieY += chartTitleHeightPlusPadding;
232
233                     legendY+= this.diameter;
234
235                     if( this.pieLabels != null )
236                     {
237                         //---adds label height from top of pie
238
this.pieY += this.pieLabels.getTallestLabel();
239                         this.pieY += properties.getTickLength();
240
241                         //---add label hight from bottom of pie
242
legendY += this.pieLabels.getTallestLabel();
243                         legendY += properties.getTickLength();
244                     }
245
246                     legendY += this.pieY;
247                     legendY+= this.getLegend().getLegendProperties().getChartPadding();
248                 }
249                 else
250                 {
251                     legendY = super.getChartProperties().getEdgePadding();
252                     legendY += chartTitleHeightPlusPadding;
253
254                     this.pieY= legendY;
255                     this.pieY+= this.getLegend().getHeight();
256                     this.pieY+= this.getLegend().getLegendProperties().getChartPadding();
257
258                     if( this.pieLabels != null )
259                     {
260                         //---adds label height from top of pie
261
this.pieY += this.pieLabels.getTallestLabel();
262                         this.pieY += properties.getTickLength();
263                     }
264                 }
265
266                 //---center the legend horizontally
267
legendX = halfImageWidth - ( this.getLegend().getWidth() / 2 );
268
269                 //---center the pie horizontally
270
this.pieX = halfImageWidth - ( this.diameter / 2 );
271             }
272
273             super.getLegend().setX( legendX );
274             super.getLegend().setY( legendY );
275             super.getLegend().render();
276
277         }
278         //---else, the Legend is NULL
279
else
280         {
281             //---if there is no legend, fill the image with the pie
282
this.diameter = Math.min( heightAvailable, widthAvailable );
283
284             float halfDiameter = this.diameter / 2;
285
286             //---center the pie horizontally
287
this.pieX = halfImageWidth - halfDiameter;
288
289             //---center the pie vertically
290
this.pieY = halfImageHeight - halfDiameter;
291         }
292
293
294         //---IMAGE MAP setup
295
//---if we are saving all the coordinates for an ImageMap, create the ImageMap Object as we
296
//--- know how many area elements there are.
297
if( super.getGenerateImageMapFlag() )
298         {
299             ImageMap imageMap = new ImageMap( iPieChartDataSet.getNumberOfDataItems() );
300             super.setImageMap( imageMap );
301         }
302
303
304         PieChart2D.render( this );
305     }
306
307
308     /************************************************************************************************
309      * Implement the method to render the Chart.
310      *
311      * @param pieChart2D
312      ************************************************************************************************/

313     static void render( PieChart2D pieChart2D )
314     {
315         Graphics2D g2d = pieChart2D.getGraphics2D();
316         PieChart2DProperties properties = (PieChart2DProperties) pieChart2D.iPieChartDataSet.getChartTypeProperties();
317
318         //---set the border Stroke
319
properties.getBorderChartStroke().setupGraphics2D( g2d );
320
321
322         //---the following only for Image Map-----------------------------
323
//---IMAGE MAP
324
//---number of subdivisions to break each slice into to 'fill' slice
325
int subdivisions = 3;
326         float halfDiameter = 0;
327         float xPieMiddle = 0;
328         float yPieMiddle = 0;
329         float imageMapPoints[][] = null;
330         if( pieChart2D.getImageMap() != null )
331         {
332             halfDiameter = (float) ( pieChart2D.diameter / 2.0 );
333             xPieMiddle = halfDiameter + pieChart2D.pieX;
334             yPieMiddle = halfDiameter + pieChart2D.pieY;
335             imageMapPoints = new float[pieChart2D.iPieChartDataSet.getNumberOfDataItems() * ( subdivisions + 1 )][2];
336         }
337
338
339         //---get the starting degree
340
float currentDegrees = properties.getZeroDegreeOffset();
341
342         double percentageOfPie = 0;
343
344
345
346         //---if only one item in chart, just draw border around outside.
347
//---if do a draw of the arc, will get a line in the pie as arc has a start and end.
348
if( pieChart2D.iPieChartDataSet.getNumberOfDataItems() == 1 )
349         {
350             Arc2D.Double JavaDoc arc = new Arc2D.Double JavaDoc( pieChart2D.pieX,
351                                                              pieChart2D.pieY,
352                                                              pieChart2D.diameter,
353                                                              pieChart2D.diameter,
354                                                              currentDegrees,
355                                                              360,
356                                                              Arc2D.OPEN );
357
358             g2d.setPaint( pieChart2D.iPieChartDataSet.getPaint( 0 ) );
359             g2d.fill( arc );
360
361             properties.getBorderChartStroke().draw( g2d, arc );
362
363             //---if only a single value use a circle map
364
//---IMAGE MAP
365
if( pieChart2D.getImageMap() != null )
366             {
367                 CircleMapArea circleMapArea = new CircleMapArea( xPieMiddle, yPieMiddle, pieChart2D.iPieChartDataSet.getValue( 0 ), null, pieChart2D.iPieChartDataSet.getLegendLabel( 0 ) );
368                 circleMapArea.setRadius( (int) pieChart2D.diameter );
369                 pieChart2D.getImageMap().addImageMapArea( circleMapArea );
370             }
371
372 // System.out.println( pieChart2D.pieLabels.getTextTag( 0 ).getText() );
373

374             if( pieChart2D.pieLabels != null )
375             {
376                 float x = pieChart2D.pieX + ( pieChart2D.diameter / 2 ) - ( pieChart2D.pieLabels.getTextTag( 0 ).getWidth() / 2 );
377                 float y = pieChart2D.pieY - properties.getTickLength();
378     // System.out.println( "x=" + x );
379
// System.out.println( "y=" + y );
380

381                 properties.getValueLabelFont().setupGraphics2D( g2d );
382                 g2d.drawString( pieChart2D.pieLabels.getTextTag( 0 ).getText(), x, y );
383             }
384         }
385         else
386         {
387             Arc2D.Double JavaDoc arc = new Arc2D.Double JavaDoc( pieChart2D.pieX,
388                                                              pieChart2D.pieY,
389                                                              pieChart2D.diameter,
390                                                              pieChart2D.diameter,
391                                                              currentDegrees,
392                                                              360,
393                                                              Arc2D.PIE );
394             //---IMAGE MAP
395
int mapCounter = 0;
396
397             for( int i = 0; i < pieChart2D.iPieChartDataSet.getNumberOfDataItems(); i++ )
398             {
399                 percentageOfPie = pieChart2D.pieChartDataProcessor.getPercentageOfPie( i );
400
401                 arc.setAngleStart( currentDegrees );
402                 arc.setAngleExtent( percentageOfPie );
403
404                 //---set the color, and fill the pie piece.
405
g2d.setPaint( pieChart2D.iPieChartDataSet.getPaint( i ) );
406                 g2d.fill( arc );
407
408                 properties.getBorderChartStroke().draw( g2d, arc );
409
410
411                 //---if we are going to display labels
412
if( pieChart2D.pieLabels != null )
413                 {
414                     //---get the angle the center of slice
415
double sliceCenterDegrees = ( currentDegrees ) + percentageOfPie / 2;
416
417                     if( sliceCenterDegrees > 360 )
418                     {
419                         sliceCenterDegrees -= 360;
420                     }
421
422
423                     double sliceCenterRadians = Math.toRadians( sliceCenterDegrees );
424
425                     //---compute the cos and sin of the label angle.
426
double cosOfLabel = Math.cos( sliceCenterRadians );
427                     double sinOfLabel = Math.sin( sliceCenterRadians );
428
429                     halfDiameter = (float) ( pieChart2D.diameter / 2.0 );
430
431
432                     //---end point of the label border line.
433
float borderXstart = (float) ( cosOfLabel * halfDiameter );
434                     float borderYstart = (float) -( sinOfLabel * halfDiameter );
435
436                     //---end point of the label border line.
437
float borderXend = (float) ( cosOfLabel * ( halfDiameter + properties.getTickLength() ) );
438                     float borderYend = (float) -( sinOfLabel * ( halfDiameter + properties.getTickLength() ) );
439
440
441                     xPieMiddle = halfDiameter + pieChart2D.pieX;
442                     yPieMiddle = halfDiameter + pieChart2D.pieY;
443
444                     properties.getValueLabelFont().setupGraphics2D( g2d );
445
446                     g2d.draw( new Line2D.Double JavaDoc( xPieMiddle + borderXstart,
447                                                           yPieMiddle + borderYstart,
448                                                           xPieMiddle + borderXend,
449                                                           yPieMiddle + borderYend ) );
450
451 //System.out.println( pieChart2D.textTagGroup.getTextTag( i ).getText() + " sliceCenterDegrees= " + sliceCenterDegrees );
452

453                     float labelY = yPieMiddle + borderYend;
454                     if( sliceCenterDegrees > 60 && sliceCenterDegrees < 120 )
455                     {
456                         labelY -= pieChart2D.pieLabels.getTextTag( i ).getFontDescent();
457                     }
458                     else if( sliceCenterDegrees > 240 && sliceCenterDegrees < 300 )
459                     {
460                         labelY += pieChart2D.pieLabels.getTextTag( i ).getFontAscent();
461                     }
462
463
464                     if( sliceCenterDegrees > 90 && sliceCenterDegrees < 270 )
465                     {
466                         g2d.drawString( pieChart2D.pieLabels.getTextTag( i ).getText(),
467                                              xPieMiddle + borderXend - pieChart2D.pieLabels.getTextTag( i ).getWidth() - properties.getTickLength(),
468                                              labelY );
469                     }
470                     else
471                     {
472                         g2d.drawString( pieChart2D.pieLabels.getTextTag( i ).getText(),
473                                              xPieMiddle + borderXend + properties.getTickLength(),
474                                              labelY );
475                     }
476                 }
477
478
479                 //---if we are generating an image map...
480
//---IMAGE MAP
481
if( pieChart2D.getImageMap() != null )
482                 {
483                     //---increment a separate amount to minimize rounding errors.
484
double workDegrees = currentDegrees;
485
486                     //---compute the cos and sin of the bodrder angle.
487
double cosOfBorder;
488                     double sinOfBorder;
489                     double splitDegree = percentageOfPie / subdivisions;
490
491                     for( int j = 0; j <= subdivisions; j++ )
492                     {
493                         cosOfBorder = Math.cos( Math.toRadians( workDegrees ) );
494                         sinOfBorder = Math.sin( Math.toRadians( workDegrees ) );
495
496                         //---end point of the slice border line.
497
imageMapPoints[mapCounter][0] = xPieMiddle + (float) ( cosOfBorder * halfDiameter );
498                         imageMapPoints[mapCounter][1] = yPieMiddle + (float) -( sinOfBorder * halfDiameter );
499
500                         //DEBUG to make sure calculating points correctly
501
//g2d.setPaint( Color.red );
502
//g2d.fillRect( (int) imageMapPoints[ mapCounter ][ 0 ], (int) imageMapPoints[ mapCounter ][ 1 ], 6, 6 );
503

504                         mapCounter++;
505                         workDegrees += splitDegree;
506                     }
507                 }
508
509                 currentDegrees += percentageOfPie;
510             }
511
512
513             //---if we are generating an image map...
514
//---IMAGE MAP
515
if( pieChart2D.getImageMap() != null )
516             {
517                 //---each slice has 3 + subdivision slices...
518
//int counter= pieChart2D.iPieChartDataSet.getNumberOfDataItems() * ( 3 + subdivisions );
519
int counter = 0;
520
521                 //---for each data item
522
for( int i = 0; i < pieChart2D.iPieChartDataSet.getNumberOfDataItems(); i++ )
523                 {
524                     int coordinateCounter = 0;
525
526                     //---there are three points plus some number of subdivisions...
527
PolyMapArea polyMapArea = new PolyMapArea( 3 + subdivisions, pieChart2D.iPieChartDataSet.getValue( i ), null, pieChart2D.iPieChartDataSet.getLegendLabel( i ) );
528                     polyMapArea.addCoordinate( coordinateCounter++, xPieMiddle, yPieMiddle );
529
530                     //---include the first border point, plus the subdivisions
531
for( int h = 0; h <= subdivisions; h++ )
532                     {
533                         polyMapArea.addCoordinate( coordinateCounter++, imageMapPoints[counter][0], imageMapPoints[counter][1] );
534                         counter++;
535                     }
536
537                     //---if this is the last slice, add the first calculated map point
538
if( ( i + 1 ) == pieChart2D.iPieChartDataSet.getNumberOfDataItems() )
539                     {
540                         polyMapArea.addCoordinate( coordinateCounter, imageMapPoints[0][0], imageMapPoints[0][1] );
541                     }
542                     //---else add the next calculated point
543
else
544                     {
545                         polyMapArea.addCoordinate( coordinateCounter, imageMapPoints[counter][0], imageMapPoints[counter][1] );
546                     }
547
548                     pieChart2D.getImageMap().addImageMapArea( polyMapArea );
549                 }
550             }
551         }
552     }
553
554
555     /**********************************************************************************************
556      * Enables the testing routines to display the contents of this Object. Override Chart
557      * implementation as PieCharts use AreaProperties directly rather than a child.
558      *
559      * @param htmlGenerator
560      * @param imageFileName
561      **********************************************************************************************/

562     public void toHTML( HTMLGenerator htmlGenerator, String JavaDoc imageFileName )
563     {
564         if( this.getLegend() != null )
565         {
566             htmlGenerator.chartTableRowStart();
567             this.getLegend().toHTML( htmlGenerator );
568             htmlGenerator.chartTableRowEnd();
569         }
570
571         htmlGenerator.chartTableEnd();
572     }
573
574 }
575
Popular Tags