//--file: CIUPieChart.java------------------------------------- // // An IU PieChart // package starwave.app.poll.imageutils; import java.math.*; import java.awt.Font; /** * * An IU PieChart primitive. The pie wedge sizes are specified * in an array of doubles, with the colors corresponding to each * wedge in an array of bytes, each byte being an index into a LUT. * * An actual array of bytes is constructed upon which to draw * the pie pieces, along with the corresponding text for the * percentages. * * The pie can have at most 256 colors -- this is imposed by * the LUT limitation. If the pie has more wedges than colors * given in it's constructor, the colors are repeated in a * round-robin fashion. * * The arcs could be drawn using the java.awt functions and then * PixelGrabbing the image, but the entire point of using the byte * array is to avoid use of these CPU intensive routines in favor of * more "lightweight" routines. * * If the pie chart is invalid ( the sum of the wedges of the pie * is off more than 1% from 100% ), the pie is drawn in full with * an error message in it's center. * * No pie chart may be constructed which has a radius less than * 20 pixels big. If a constructor is invoked with a radius of * less than 20 pixels the given value is overridden and assigned * a new value of 20. * * The pie chart is chosen to "begin" at an angle of 0 degrees * because this maximizes the number of small percentages which can * have text associated with them. Many 2% and 3% wedges will still * be labelled due to the fact that they're located close to an * angle of 0 degrees on the pie chart. * */ public class CIUPieChart extends CIUPrimitive { /** The number of pie pieces */ private int m_num; /** The radius of the pie */ private int m_rad; /** The array of pie piece colors */ private byte [] m_colors = null; /** The array of pie piece sizes */ private double [] m_sizePer = null; /** The array of pie piece sizes */ private double [] m_sizeDeg = null; /** The array of offset angles */ private double [] m_offset = null; /** The center arc angle for each piece */ private double [] m_offsetY = null; /** The center arc angle for each piece */ private double [] m_offsetX = null; /** The color of the font for text */ private byte m_textColor; /** The color of the background */ private byte m_bgColor; /** The CIUFrame used to draw text with... */ private CIUFrame m_frame = null; /** The extra storage necessary to create the primitive */ private CIUByteArray m_bytedata = null; /** The extra storage necessary to create the primitive */ private CIUBitArray m_bitdata = null; /** Starting angle for pie wedges */ private int m_startAngle = 30; /** A reference to the LUT used to create the Pie Chart */ private CIUNetscapeColorCube m_lut = null; /** The minimum angle before rendering problems start occurring */ private static final double m_minAngle = 5.0; /** The offset magnitude for the pie wedges */ private int m_dR; /** Percentage text font (12 point) */ public static final CIUFont m_fP12 = new CIUFont( new Font( "TimesRoman", Font.BOLD, 12 ) ); /** Percentage text font (11 point) */ public static final CIUFont m_fP11 = new CIUFont( new Font( "TimesRoman", Font.BOLD, 11 ) ); /** Percentage text font (10 point) */ public static final CIUFont m_fP10 = new CIUFont( new Font( "TimesRoman", Font.BOLD, 10 ) ); /** Percentage text font (9 point) */ public static final CIUFont m_fP09 = new CIUFont( new Font( "TimesRoman", Font.BOLD, 9 ) ); /** Percentage text font (8 point) */ public static final CIUFont m_fP08 = new CIUFont( new Font( "TimesRoman", Font.BOLD, 8 ) ); /** ************************************************************* * * Construct a pie chart. The pie can have no more than 255 * wedges if each wedge is to be a different color. * * @param r Radius * @param explode Should the pie wedges be exploded or left in place? * @param sizes The array of pie piece sizes in % out of 100% * @param colors The array of pie chart colors as LUT indexes * @param frame The CIUFrame used for drawing text primitives * @param textcolor The color of the font * @param bgcolor The color of the background * @param cc The LUT * */ public CIUPieChart( int r, boolean explode, double [] sizes, byte [] colors, CIUFrame frame, byte textcolor, byte bgcolor, CIUNetscapeColorCube cc ) { // // Add room to "explode" the pie wedges - r is the overall width of the chart // super( ((r>=20)?( (r)*2 ):(20*2)), ((r>=20)?( (r)*2 ):(20*2)) ); boolean bFatality = false; if (r<20) { System.err.println("CIUPieChart::CIUPieChart> Invalid radius [" + r + "] Setting to minumum of 20" ); r=20; } if ( null==sizes ) { System.err.println("CIUPieChart::CIUPieChart> Null percentage array...fatality."); bFatality = true; } if ( null==colors ) { System.err.println("CIUPieChart::CIUPieChart> Null color array...fatality."); bFatality = true; } if ( null==frame ) { System.err.println("CIUPieChart::CIUPieChart> Null frame...fatality."); bFatality = true; } if ( null==cc ) { System.err.println("CIUPieChart::CIUPieChart> Null color cube...fatality."); bFatality = true; } if ( true == bFatality ) { pieFatality(); return; } if ( true == explode ) { if ( r>=100 ) m_dR = 3; else if ( r>= 60 ) m_dR = 3; else if ( r>= 35 ) m_dR = 2; else m_dR = 0; } else { m_dR = 0; } // Actual radius is (r-m_dR) m_rad = (r-m_dR-1); // Initialization and capturing of member variables m_textured = true; m_bytedata = new CIUByteArray( m_w, m_h ); m_num = sizes.length; m_colors = colors; m_textColor = textcolor; m_bgColor = bgcolor; m_sizePer = sizes; m_lut = cc; m_bytedata.fill( m_bgColor ); m_frame = frame; // Sort the indexes from lowest to highest crappieSortWedges(); // Calculate dx/dy offsets for each wedge of the pie if ( calculateOffsets() ) { // Draw the actual wedges into the byte array drawPieWedges(); // Draw the text for each wedge drawPieText(); } else { // Pie is invalid - percents sum ouside [98..102] pieFatality(); } return; } /** ************************************************************* * * Construct a pie chart. The colors of the wedges correspond * to the CIUColors in the color array * * @param r Radius * @param explode Should the pie wedges be exploded or left in place? * @param sizes The array of pie piece sizes * @param c The array of pie chart colors as CIUColors * @param f The CIUFrame used for drawing text primitives * @param t The color of the font as a CIUColor * @param b The color of the background as a CIUColor * @param cc The LUT * */ public CIUPieChart( int r, boolean explode, double [] sizes, CIUColor [] c, CIUFrame f, CIUColor t, CIUColor b, CIUNetscapeColorCube cc ) { this( r, explode, sizes, cc.toLUTIndexes(c), f, (byte)(cc.toLUTIndex(t)), (byte)(cc.toLUTIndex(b)), cc ); } /** ************************************************************* * * Construct a pie chart. The colors of the wedges correspond * to the HEX strings in the color array * * @param r Radius * @param explode Should the pie wedges be exploded or left in place? * @param sizes The array of pie piece sizes * @param c The array of pie chart colors as HEX strings * @param f The CIUFrame used for drawing text primitives * @param t The color of the font as a HEX string * @param b The color of the background as a HEX string * @param cc The LUT * */ public CIUPieChart( int r, boolean explode, double [] sizes, CIUFrame f, String [] c, String t, String b, CIUNetscapeColorCube cc ) { this( r, explode, sizes, cc.toLUTIndexes(c), f, (byte)(cc.toLUTIndex(t)), (byte)(cc.toLUTIndex(b)), cc ); } /** ************************************************************* * * Calculate the pie wedges and fill in the byte array with the * proper index into the array of colors. * */ private void drawPieWedges() { double radius; double px, py; double angle; double current; boolean foundWedge; try { for ( int j=m_rad; j>=-m_rad; j-- ) { for ( int i=-m_rad; im_rad ) py -= (m_offsetY[n]); else py += (m_offsetY[n]); // At last, do integer rounding to pixel/array value int index = (m_dR+m_rad-((int)(py))-1)*m_w + m_dR + (((int)(px))+m_rad); // Set the correct byte in the byte array m_bytedata.setByte( index, m_colors[ n % m_colors.length ] ); foundWedge = true; } } // End of "for" } // End of "if ( radius<= m_rad )..." } } } catch (Exception e ) { System.err.println("CIUPieChart::calculatePieWedges> Exception: " + e ); } return; } /** ************************************************************* * * Determine the size of the font for rendering the percentages * on a pie. It needs to be the same size for each wedge of a * pie -- therefore it is dependent on the radius only. * */ private CIUFont percentageFont() { if ( m_rad<40 ) return (m_fP08); else if ( m_rad<55 ) return (m_fP09); else if ( m_rad<70 ) return (m_fP10); else if ( m_rad<85 ) return (m_fP11); else return (m_fP12); } /** **************************************************************** * * Determine if the wedge is big enough to have any text on it * at all. This is a function of the radius of the pie and the * size of the wedge (in percent). This is static because the * logic needs to be accessible by other classes. * * @param The radius of the pie */ public static boolean percentageTextAdded( int radius, double percent ) { double cutoff = 1.0; if ( radius<30 ) cutoff=99.999999; else if ( radius<36 ) cutoff=20.0; else if ( radius<50 ) cutoff = (46.0-0.8*radius); else if (radius<130 ) cutoff = (5.765-0.03529*radius); return ( (percent= 100 ) // Centered return (0.0); if ( m_rad >= 40 ) { if ( percent < 5 ) explode = 0.9; else explode = 0.7; } else if ( m_rad >= 35 ) { explode = 0.65; } else explode = 0.5; return (explode); } /** ************************************************************* * * Calculate the centroid of each piece of text and then * translate the text so it's centroid is along the radial * center of the arc. This is different than what happens for * the graphics context. * * Four variables for each piece of text: * * o What size should the text be? * o Should there be text this wedge? * o Should there be a "%" char at the end of the text? * o How far (radially outward) should the text be located? * */ private void drawPieText() { CIUPrimitive p = null; CIUFont font = null; String text = null; int bArrayOffset; double explode; byte color; // Iterate over the wedges to be drawn.... for ( int n=0; nm_w ) eX=m_w-cX; if ( (cY+cH)>m_h ) eY=m_h-cY; // the starting point in the byte array (translation of text) if ( cX<0 && cY<0 ) bArrayOffset = 0; else if ( cX<0 && cY>0 ) bArrayOffset = (m_w*cY); else if ( cX>0 && cY<0 ) bArrayOffset = cX; else bArrayOffset = (m_w*cY) + cX; try { for ( int j=0; j<(eY-sY); j++, bArrayOffset+=m_w ) for ( int i=0; i<(eX-sX); i++ ) if ( p.pixelOn( i+sX, j+sY ) ) this.m_bytedata.setByte( (bArrayOffset+i), color ); } catch ( Exception e ) { System.err.println("CIUPieChart::drawPieText> Exception: " + e ); System.err.println("CIUPieChart::drawPieText> Skipping this text: " + text ); } p.dispose(); p = null; } // End of "if ( percentageTextAdded... " font = null; text = null; } // End of "for ( int n=0; n Pie exception: " + e ); } // // Create error message and center at origin f = percentageFont(); // // See if we need to contrast the message text color = m_textColor; textColor = m_lut.iuColor( m_textColor ); iIndex= CIUBitArray.byteToInt( m_colors[ 0 ] ); wedgeColor = m_lut.iuColor( iIndex ); if ( false == textColor.inContrast( wedgeColor ) ) color = (byte)m_lut.toLUTIndex( textColor.getContrast() ); if (m_rad>10) { if (m_rad<35) err="Err"; else if (m_rad<60) err="Data error"; else err="The pie chart data cannot be displayed"; p = new CIUText( err, (int)(m_w/2.0), f, m_frame, m_textColor ); int pW = p.width(); int pH = p.height(); int pX = (int)(m_rad-pW/2.0); int pY = (int)(m_rad-pH/2.0); int bArrayOffset = (m_w*pY) + pX; try { for ( int j=0; j Text exception: " + e ); } } return; } /** ************************************************************* * * Crappie sort routine to sort the wedges of the pie into * ascending order for display. * */ private void crappieSortWedges() { byte tB1; double tD1; for ( int i=1; i101.0 ) return false; return true; } /** ************************************************************ * * Get the angle [0..360] associated with this x,y pair * * atan: Returns the arc tangent of an angle, in the range * of -pi/2 through pi/2. * */ public double getAngle( int x, int y ) { double dx = (double)x; double dy = (double)y; // Quadrant I [0..90] if ( x>0 && y>0 ) return ( 0.5*Math.PI - Math.atan(dx/dy) ); // Quadrant II [90..180] if ( x<0 && y>0 ) return ( 0.5*Math.PI + ( -1*Math.atan(dx/dy) ) ); // Quadrant III [180..270] if ( x<0 && y<0 ) return ( Math.PI + (0.5*Math.PI - Math.atan(dx/dy)) ); // Quadrant IV [270..360] if ( x>0 && y<0 ) return ( 1.5*Math.PI + (-1*Math.atan(dx/dy)) ); if ( 0==y ) { if ( x>=0 ) return 0; else return Math.PI; } if ( 0==x ) { if ( y>=0 ) return 0.5*Math.PI; else return 1.5*Math.PI; } return -1.0; // this should never execute } /** ************************************************************ * * Utility to convert an angle in radians to an angle in * degrees * */ public static double rad2deg( double rad ) { return ( (rad*180.0/Math.PI) ); } /** ************************************************************ * * Utility to convert an angle in degrees to an angle in * radians * */ public static double deg2rad( double deg ) { return ( deg/180.0*Math.PI ); } /** ************************************************************ * * pixelOn: Indicate if the pixel is on at position x,y * * @param x The x position of the pixel of interest. * @param y The y position of the pixel of interest. * @returns boolean Indicates if pixel is "on" or "off" * * @throws Exception on array-out-of-bounds pixel request * @exception Exception on array-out-of-bounds pixel request * */ public boolean pixelOn( int x, int y ) throws Exception { if ( x>=m_w || y>=m_h || x<0 || y<0 ) throw new Exception("CIUPieChart::pixelOn> Invalid x/y request: " + x + "/" + y ); return true; } /** ************************************************************* * * Color accessor - given an X/Y pair, return the IUColor * at that coordinate. * * @param x The x-coordinate of interest * @param x The y-coordinate of interest * @returns byte An index into the LUT for the wedge color, or * test color, or background color. * * @throws Exception on array-out-of-bounds pixel request * @exception Exception on array-out-of-bounds pixel request * */ public byte color( int x, int y ) throws Exception { if ( x>=m_w || y>=m_h || x<0 || y<0 ) throw new Exception("CIUPieChart::color> Invalid x/y request: " + x + "/" + y ); return ( m_bytedata.getByte( y*m_w + x) ); } /** ************************************************************* * * Rotate the piechart - this may never be implemented. * * @param angle The angle to rotate through * @returns this * */ public CIUPrimitive rotate( double angle ) { return this; } /** ************************************************************* * * Scale the pie chart - this may never be implemented. * * @param scalar The scalar * @returns this * */ public CIUPrimitive scale( double scalar ) { return this; } /** ************************************************************* * * Convert the piechart to a string representation. * * @returns String The piechart as a string * */ public String toString() { String ret = ""; try { for ( int i=0; i Exception: " + e ); } return (ret); } /** ***************************************************************** * * Free up any resources associated with this object * */ public void dispose() { m_frame = null; m_colors = null; m_sizePer = null; m_sizeDeg = null; m_offset = null; m_offsetY = null; m_offsetX = null; m_bytedata = null; m_bitdata = null; m_lut = null; } }