001    /* 
002     * Copyright 2007,2008,2009 John C. Gunther
003     * 
004     * Licensed under the Apache License, Version 2.0 (the
005     * "License"); you may not use this file except in compliance
006     * with the License. You may obtain a copy of the License at:
007     * 
008     *  http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing,
011     * software distributed under the License is distributed on an
012     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013     * either express or implied. See the License for the specific
014     * language governing permissions and limitations under the
015     * License.
016     * 
017     */
018    package com.googlecode.gchart.client;
019    
020    import com.google.gwt.i18n.client.DateTimeFormat;
021    import com.google.gwt.i18n.client.NumberFormat;
022    import com.google.gwt.user.client.DOM;
023    import com.google.gwt.user.client.Window;
024    import com.google.gwt.dom.client.Element;
025    import com.google.gwt.dom.client.EventTarget;
026    import com.google.gwt.event.dom.client.ClickEvent;
027    import com.google.gwt.event.dom.client.ClickHandler;
028    import com.google.gwt.event.dom.client.HasClickHandlers;
029    import com.google.gwt.event.shared.HandlerRegistration;
030    import com.google.gwt.user.client.Event;
031    import com.google.gwt.user.client.ui.AbsolutePanel;
032    import com.google.gwt.user.client.ui.Composite;
033    import com.google.gwt.user.client.ui.Grid;
034    import com.google.gwt.user.client.ui.HasHTML;
035    import com.google.gwt.user.client.ui.HasHorizontalAlignment;
036    import com.google.gwt.user.client.ui.HasText;
037    import com.google.gwt.user.client.ui.HasVerticalAlignment;
038    import com.google.gwt.user.client.ui.HTML;
039    import com.google.gwt.user.client.ui.Image;
040    import com.google.gwt.user.client.ui.SimplePanel;
041    import com.google.gwt.user.client.ui.UIObject;
042    import com.google.gwt.user.client.ui.Widget;
043    import java.util.ArrayList;
044    import java.util.Date;
045    import com.google.gwt.core.client.GWT;
046    
047    /**
048     * A GChart can represent and display a line chart, a bar chart,
049     * a pie chart, an area chart, or a chart that contains arbitrary
050     * combinations of line, bar, pie, and/or area based curves.
051     * 
052     * <p>
053     * For detailed examples, with screen shots, visit the
054     * <a href="package-summary.html#ChartGallery">
055     * Chart Gallery</a>. 
056     * 
057     * <p>
058     * For detailed instructions on how to integrate Client-side GChart
059     * into your GWT application, see
060     * <a href="package-summary.html#InstallingGChart">
061     * Installing Client-side GChart</a>.
062     * 
063     * <p>
064     * <b>CSS Style Rule</b>
065     * <ul>
066     * .gchart-GChart { the GChart's primary top-level styles }
067     * </ul>
068     *
069     *
070     * It is sometimes more natural to consider certain CSS
071     * attributes as properties of a GChart Java object. So, GChart
072     * supports "CSS convenience methods" that let you (optionally) use
073     * Java to specify GChart CSS attributes such as
074     * <tt>border-color</tt> and <tt>background-color</tt>. See
075     * {@link #USE_CSS USE_CSS} for a detailed description of these
076     * CSS convenience methods--which won't interfere with standard
077     * CSS-based specifications if you never invoke them.
078     *
079     * 
080     **/ 
081    
082    public class GChart extends Composite implements HasClickHandlers {
083    
084       
085       /**
086        ** Defines the location of a data point's annotation or hover
087        ** annotation (which can be defined by either plain text, HTML,
088        ** or a widget) relative to the location of that point's
089        ** symbol.  The "Field Summary"
090        ** section below lists all available annotation locations.
091        ** <p>
092        **
093        ** The default annotation location is {@link
094        ** AnnotationLocation#SOUTH SOUTH} for annotations and
095        ** is symbol-type-dependent for hover annotations. See the
096        ** <tt>setHoverLocation</tt> method for list of these defaults.
097        ** 
098        ** <p>
099        ** 
100        ** You can further adjust the position of a point's
101        ** annotation (or hover annotation) by specifying non-zero
102        ** positional shifts via the <tt>setAnnotationXShift</tt>
103        ** and <tt>setAnnotationYShift</tt> (or via the
104        ** <tt>setHoverXShift</tt>, <tt>setHoverYShift</tt>),
105        ** and <tt>setHoverAnnotationSymbolType</tt> methods for
106        ** hover annotations).
107        ** <p>
108        ** 
109        ** @see Curve.Point#setAnnotationLocation Point.setAnnotationLocation
110        ** @see Curve.Point#setAnnotationXShift Point.setAnnotationXShift
111        ** @see Curve.Point#setAnnotationYShift Point.setAnnotationYShift
112        ** @see Symbol#setHoverLocation Symbol.setHoverLocation
113        ** @see Symbol#setHoverAnnotationSymbolType
114        ** Symbol.setHoverAnnotationSymbolType
115        ** @see Symbol#setHoverXShift Symbol.setHoverXShift
116        ** @see Symbol#setHoverYShift Symbol.setHoverYShift
117        ** @see #DEFAULT_HOVER_LOCATION DEFAULT_HOVER_LOCATION
118        ** 
119        **/ 
120       public static final class AnnotationLocation {
121    // non-public tagging-only locations used by ANCHOR_MOUSE_* symbol types
122          static final AnnotationLocation AT_THE_MOUSE =
123            new AnnotationLocation(0,0);
124          static final AnnotationLocation AT_THE_MOUSE_SNAP_TO_X =
125            new AnnotationLocation(0,0);
126          static final AnnotationLocation AT_THE_MOUSE_SNAP_TO_Y =
127            new AnnotationLocation(0,0);
128          /**
129           ** Specifies that a point's annotation (label) should
130           ** be positioned so as to be centered on the symbol
131           ** used to represent the point.
132           **
133           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
134           **/
135          public static final AnnotationLocation CENTER =
136            new AnnotationLocation(0,0);
137    
138          private static final AnnotationLocation north =
139             new AnnotationLocation(0,-1);
140          private static final AnnotationLocation west =
141            new AnnotationLocation(-1, 0);
142          private static final AnnotationLocation south =
143              new AnnotationLocation(0, 1);
144          
145          /**
146           ** Specifies that a point's annotation (label) should be
147           ** placed just above, and centered horizontally on,
148           ** vertical bars that grow down from a horizontal
149           ** baseline, and just below, and centered horizontally on,
150           ** vertical bars that grow up from a horizontal baseline.
151           **
152           ** <p>
153           **
154           ** This another name for
155           ** <tt>AnnotationLocation.NORTH</tt>. Its sole purpose is
156           ** to clarify/document the behavior of this location type
157           ** when used in conjunction with curves that employ 
158           ** <tt>VBAR_BASELINE_*</tt> symbol types.
159           **
160           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
161           ** @see SymbolType#VBAR_BASELINE_CENTER SymbolType.VBAR_BASELINE_CENTER
162           ** 
163           **/
164          public static final AnnotationLocation 
165              CLOSEST_TO_HORIZONTAL_BASELINE = north;
166          
167          /**
168           ** Specifies that a point's annotation (label) should be
169           ** placed just to the right of, and centered vertically
170           ** on, horizontal bars that grow left from a vertical
171           ** baseline, and just to the left of, and centered
172           ** vertically on, horizontal bars that grow right from a
173           ** vertical baseline.
174           **
175           ** <p>
176           **
177           ** This another name for
178           ** <tt>AnnotationLocation.WEST</tt>. Its sole purpose is
179           ** to clarify/document the behavior of this location type
180           ** when used in conjunction with curves that employ the
181           ** <tt>HBAR_BASELINE_*</tt> symbol types.
182           **
183           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
184           ** @see SymbolType#HBAR_BASELINE_CENTER SymbolType.HBAR_BASELINE_CENTER
185           ** 
186           **/
187           
188          public static final AnnotationLocation 
189             CLOSEST_TO_VERTICAL_BASELINE = west;
190          
191          /**
192           ** Specifies that a point's annotation (label) should
193           ** be positioned just to the right of, and vertically
194           ** centered on, the symbol used to represent the
195           ** point.
196           **
197           ** @see Curve.Point#setAnnotationLocation
198           **/
199          public static final AnnotationLocation EAST =
200             new AnnotationLocation(1, 0);
201    
202          /**
203           ** Specifies that a point's annotation (label) should be
204           ** placed just below, and centered horizontally on,
205           ** vertical bars that grow down from a horizontal
206           ** baseline, and just above, and centered horizontally on,
207           ** vertical bars that grow up from a horizontal baseline.
208           **
209           ** <p>
210           **
211           ** This another name for
212           ** <tt>AnnotationLocation.SOUTH</tt>. Its sole purpose is
213           ** to clarify/document the behavior of this location type
214           ** when used in conjunction with curves that employ
215           ** <tt>VBAR_BASELINE_*</tt> symbol types.
216           **
217           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
218           ** @see SymbolType#VBAR_BASELINE_CENTER SymbolType.VBAR_BASELINE_CENTER
219           ** 
220           **/
221          public static final AnnotationLocation
222              FARTHEST_FROM_HORIZONTAL_BASELINE = south;
223          
224          /**
225           ** Specifies that a point's annotation (label) should be
226           ** placed just to the left of, and centered vertically on,
227           ** horizontal bars that grow left from a vertical
228           ** baseline, and just to the right of, and centered
229           ** vertically on, horizontal bars that grow right from a
230           ** vertical baseline.
231           **
232           ** <p>
233           **
234           ** This another name for
235           ** <tt>AnnotationLocation.EAST</tt>. Its sole purpose is
236           ** to clarify/document the behavior of this location type
237           ** when used in conjunction with curves that employ the
238           ** <tt>HBAR_BASELINE_*</tt> family of symbol types.
239           **
240           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
241           ** @see SymbolType#HBAR_BASELINE_CENTER SymbolType.HBAR_BASELINE_CENTER
242           ** 
243           **/
244          public static final AnnotationLocation 
245              FARTHEST_FROM_VERTICAL_BASELINE = EAST;
246          
247    
248          /**
249           ** Specifies that a point's annotation (label) should
250           ** be positioned just inside, and centered on, the
251           ** arc side of a pie slice.
252           ** <p>
253           ** 
254           ** You can move a pie slice's annotation a specific number
255           ** of pixels radially away from (or towards) the pie
256           ** center by passing a positive (or negative) argument to
257           ** the associated <tt>Point</tt>'s
258           ** <tt>setAnnotationXShift</tt> method.
259           ** 
260           ** <p> This is pie-friendly synonym for, and when used
261           ** with non-pie symbol types will behave exactly the same
262           ** as, <tt>AnnotationLocation.NORTH</tt>
263           **      
264           ** @see #OUTSIDE_PIE_ARC OUTSIDE_PIE_ARC
265           ** @see #ON_PIE_ARC ON_PIE_ARC
266           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
267           ** @see AnnotationLocation#NORTH NORTH
268           **/
269          public static final AnnotationLocation INSIDE_PIE_ARC = north;
270    
271          /**
272           ** Specifies that a point's annotation (label) should
273           ** be positioned just above, and horizontally centered on,
274           ** the symbol used to represent the point.
275           **
276           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
277           **/
278          public static final AnnotationLocation NORTH = north;
279    
280    
281          /**
282           ** Specifies that a point's annotation (label) should
283           ** be positioned just to the right of and above,
284           ** the symbol used to represent the
285           ** point.
286           **
287           ** @see Curve.Point#setAnnotationLocation
288           **/
289          public static final AnnotationLocation NORTHEAST =
290            new AnnotationLocation(1, -1);
291    
292          /**
293           ** Specifies that a point's annotation (label) should
294           ** be positioned just to the left of and above,
295           ** the symbol used to represent the
296           ** point.
297           **
298           ** @see Curve.Point#setAnnotationLocation
299           **/
300          public static final AnnotationLocation NORTHWEST =
301            new AnnotationLocation(-1, -1);
302    
303          
304          /**
305           ** Specifies that a point's annotation (label) should
306           ** be centered on the center-point of the
307           ** arc side of a pie slice.
308           ** <p>
309           **
310           ** You can move a pie slice's annotation a specific number
311           ** of pixels radially away from (or towards) the pie
312           ** center by passing a positive (or negative) argument to
313           ** the associated <tt>Point</tt>'s
314           ** <tt>setAnnotationXShift</tt> method.
315           ** 
316           **
317           ** 
318           ** <p> This is pie-friendly synonym for, and when used
319           ** with non-pie symbol types will behave exactly the same
320           ** as, <tt>AnnotationLocation.CENTER</tt>
321           **
322           ** @see #OUTSIDE_PIE_ARC OUTSIDE_PIE_ARC
323           ** @see #INSIDE_PIE_ARC INSIDE_PIE_ARC
324           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
325           ** @see AnnotationLocation#CENTER CENTER
326           ** 
327           **/
328          public static final AnnotationLocation ON_PIE_ARC = CENTER;
329    
330          /**
331           ** Specifies that a point's annotation (label) should
332           ** be positioned just outside, and centered on, the
333           ** arc side of a pie slice.
334           ** <p>
335           ** 
336           ** You can move a pie slice's annotation a specific number
337           ** of pixels radially away from (or towards) the pie
338           ** center by passing a positive (or negative) argument to
339           ** the associated <tt>Point</tt>'s
340           ** <tt>setAnnotationXShift</tt> method.
341           ** 
342           ** <p> This is pie-friendly synonym for, and when used
343           ** with non-pie symbol types will behave exactly the same
344           ** as, <tt>AnnotationLocation.SOUTH</tt>
345           **
346           ** @see #INSIDE_PIE_ARC INSIDE_PIE_ARC
347           ** @see #ON_PIE_ARC ON_PIE_ARC
348           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
349           ** @see Curve.Point#setAnnotationXShift setAnnotationXShift
350           ** @see AnnotationLocation#SOUTH SOUTH
351           **/
352          public static final AnnotationLocation OUTSIDE_PIE_ARC = south;
353    
354          /**
355           ** Specifies that a point's annotation (label) should
356           ** be positioned just below, and horizontally centered on,
357           ** the symbol used to represent the point.
358           **
359           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
360           **/
361          public static final AnnotationLocation SOUTH = south;
362    
363          
364          /**
365           ** Specifies that a point's annotation (label) should
366           ** be positioned just to the right of and below,
367           ** the symbol used to represent the
368           ** point.
369           **
370           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
371           **/
372          public static final AnnotationLocation SOUTHEAST =
373            new AnnotationLocation(1, 1);
374          /**
375           ** Specifies that a point's annotation (label) should
376           ** be positioned just to the left of and below,
377           ** the symbol used to represent the
378           ** point.
379           **
380           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
381           **/
382          public static final AnnotationLocation SOUTHWEST =
383            new AnnotationLocation(-1, 1);
384    
385          /**
386           ** Specifies that a point's annotation (label) should
387           ** be positioned just to the left of, and vertically
388           ** centered on, the symbol used to represent the
389           ** point.
390           **
391           ** @see Curve.Point#setAnnotationLocation setAnnotationLocation
392           **/
393          public static final AnnotationLocation WEST = west;
394    
395          
396          
397          // these multiply the width and height of the annotation and
398          // the symbol it is attached to in order to define the
399          // center of the annotation (see equations in later code),
400          // and thus the upper left corner anchoring point.
401          private int heightMultiplier;
402          private int widthMultiplier;
403          private AnnotationLocation(int widthMultiplier,
404                                 int heightMultiplier) {
405            validateMultipliers(widthMultiplier, heightMultiplier);
406            this.widthMultiplier = widthMultiplier;
407            this.heightMultiplier = heightMultiplier;
408          }
409          // retrieves a static location given its multipliers
410          private static AnnotationLocation getAnnotationLocation(
411             int widthMultiplier, int heightMultiplier) {
412            final AnnotationLocation[][] locationMap = {
413             {NORTHWEST, NORTH, NORTHEAST},
414             {WEST, CENTER, EAST},
415             {SOUTHWEST, SOUTH, SOUTHEAST}};
416       // assumes both multiplier are -1, 0, or 1   
417            AnnotationLocation result =
418             locationMap[heightMultiplier+1][widthMultiplier+1];
419           return result;                
420          }
421                                                       
422          // Negative width or height "turn the symbol inside-out",
423          // requiring a corresponding "reflection" of annotation
424          // location (only needed for baseline-based bar symbols)
425          static AnnotationLocation transform(AnnotationLocation a,
426                                              int signWidth,
427                                              int signHeight) {
428            AnnotationLocation result = a;
429            if (signWidth < 0 || signHeight < 0) 
430               result = getAnnotationLocation(
431                    signWidth*a.widthMultiplier,
432                    signHeight*a.heightMultiplier);
433    
434            return result;
435          }
436          // These define the alignment of the label within it's
437          // containing 1 x 1 Grid. For example, if this
438          // containing grid is to the left of the labeled
439          // symbol (widthMultiplier==-1) the horizontal
440          // alignment will be ALIGN_RIGHT, so as to bring the
441          // contained label flush against the left edge of the
442          // labeled symbol.
443          HasHorizontalAlignment.HorizontalAlignmentConstant
444                getHorizontalAlignment() {
445             HasHorizontalAlignment.HorizontalAlignmentConstant result;
446             if (widthMultiplier == -1)
447                result = HasHorizontalAlignment.ALIGN_RIGHT;
448             else if (widthMultiplier == 0)
449                result = HasHorizontalAlignment.ALIGN_CENTER;
450             else if (widthMultiplier == 1)
451                result = HasHorizontalAlignment.ALIGN_LEFT;
452             else
453                throw new IllegalStateException(
454                   "Invalid widthMultiplier: " + widthMultiplier +
455                   " 1, 0, or -1 were expected.");
456             return result;
457          }
458    
459          /* Given the x-coordinate at the center of the symbol
460           * that this annotation annotates, the annotation's
461           * width, and the symbol's width, this method returns
462           * the x-coordinate of the upper left corner of
463           * this annotation.
464           */
465          int getUpperLeftX(double x, double w, double symbolW) {
466             int result = (int) Math.round(x +
467               (widthMultiplier * (w + symbolW) - w)/2.);
468             return result;
469          }
470    
471          /* analogous to getUpperLeftX, except for the y-coordinate */
472          int getUpperLeftY(double y, double h, double symbolH) {
473             int result = (int) Math.round(y +
474               (heightMultiplier * (h + symbolH) - h)/2.);
475             return result;
476          }
477          // analogous to getHorizontalAlignment  
478          HasVerticalAlignment.VerticalAlignmentConstant
479                getVerticalAlignment() {
480             HasVerticalAlignment.VerticalAlignmentConstant result;
481             if (heightMultiplier == -1)
482                result = HasVerticalAlignment.ALIGN_BOTTOM;
483             else if (heightMultiplier == 0)
484                result = HasVerticalAlignment.ALIGN_MIDDLE;
485             else if (heightMultiplier == 1)
486                result = HasVerticalAlignment.ALIGN_TOP;
487             else
488                throw new IllegalStateException(
489                   "Invalid heightMultiplier: " + heightMultiplier +
490                   " -1, 0, or 1 were expected.");
491             return result;
492          }
493    
494    
495           /*
496            * This method returns the annotation location whose
497            * "attachment point" keeps the annotation either
498            * completely outside, centered on, or completely inside
499            * (depending on if the heightMultiplier of this annotation
500            * is 1, 0, or -1) the point on the pie's circumference
501            * associated with the given angle.
502            * <p>
503            *
504            * The use of heightMultiplier rather than widthMultiplier
505            * is somewhat arbitrary, but was chosen so that the
506            * NORTH, CENTER, and SOUTH annotation locations have the
507            * same interpretation for a pie slice whose bisecting
508            * radius points due south (due south is the default initial
509            * pie slice orientation) and for a 1px x 1px BOX_CENTER
510            * type symbol positioned at the due south position on the
511            * pie's circumference. As the pie-slice-arc-bisection
512            * point moves clockwise around the pie perimeter, the
513            * attachment point (except for vertically-centered
514            * annotations, which remain centered on the pie arc) also
515            * moves clockwise, but in discrete jumps (e.g. from
516            * NORTH, to NORTHEAST, to EAST, to SOUTHEAST, to SOUTH,
517            * etc. for annotations inside the pie) so the annotation
518            * remains appropriately attached to the center of the
519            * slice's arc as the angle changes.
520            * 
521            */
522         AnnotationLocation decodePieLocation(double thetaMid) {
523            // a sin or cos that is small enough so that the
524            // associated angle is horizontal (for sines) or vertical
525            // (for cosines) enough to warrant use of a "centered"
526            // annotation location.
527           final double LOOKS_VERTICAL_OR_HORIZONTAL_DELTA = 0.1; 
528           double sinTheta = Math.sin(thetaMid); 
529           double cosTheta = Math.cos(thetaMid); 
530           int pieTransformedWidthMultiplier = heightMultiplier *
531              ((cosTheta < -LOOKS_VERTICAL_OR_HORIZONTAL_DELTA)? -1 :
532              ((cosTheta > LOOKS_VERTICAL_OR_HORIZONTAL_DELTA)? 1 : 0));
533           int pieTransformedHeightMultiplier = heightMultiplier *
534              ((sinTheta < -LOOKS_VERTICAL_OR_HORIZONTAL_DELTA)? 1 :
535              ((sinTheta > LOOKS_VERTICAL_OR_HORIZONTAL_DELTA)? -1 : 0));
536                 
537           return getAnnotationLocation(pieTransformedWidthMultiplier,
538                                        pieTransformedHeightMultiplier);
539             
540         }
541          
542       } // end of class AnnotationLocation
543    
544    
545      /**
546       ** Represents an axis of the chart, for example, the x,
547       ** y, or y2 axis. An axis consists of the axis itself,
548       ** along with its tick marks, tick labels and gridlines.
549       **
550       ** @see XAxis XAxis
551       ** @see YAxis YAxis
552       ** @see Y2Axis Y2Axis
553       ** @see #getXAxis getXAxis 
554       ** @see #getYAxis getYAxis
555       ** @see #getY2Axis getY2Axis
556       ** 
557       **
558       **/ 
559       public abstract class Axis {
560         protected boolean isHorizontalAxis; // true for X, false for Y
561         protected int ticksId;              // sys curve representing ticks
562         protected int gridlinesId;          // sys curve representing gridlines
563         protected int axisId;               // sys curve representing axis line
564         protected int axisPosition;         // +/-1 for right/left axes
565         protected TickLocation tickLocation = DEFAULT_TICK_LOCATION;
566         private int nCurvesVisibleOnAxis = 0;  // # of developer curves on axis.
567                                           // (count does not include system or
568                                           // invisible curves)
569    
570         void incrementCurves() {nCurvesVisibleOnAxis++;}
571         void decrementCurves() {nCurvesVisibleOnAxis--;}
572         
573         protected class AxisLimits { 
574            double min; double max; // in user-defined model units
575            AxisLimits(double min, double max) {
576               this.min = min;
577               this.max = max;
578            }
579            boolean equals(AxisLimits al) {
580               boolean result =  (al.min == min && al.max == max);
581               return result;
582            }
583         }
584    
585         // different initial curr, prev ==> "limits have changed" state
586         private AxisLimits currentLimits = new AxisLimits(
587            Double.MAX_VALUE, -Double.MAX_VALUE);
588         private AxisLimits previousLimits = new AxisLimits(
589            -Double.MAX_VALUE, Double.MAX_VALUE);
590         
591         private Widget axisLabel;
592         protected int axisLabelThickness = GChart.NAI;
593         private boolean hasGridlines = false;
594         protected int tickCount = DEFAULT_TICK_COUNT;
595    // axes auto-scale whenever min or max are NaN.
596         protected double axisMax = Double.NaN;
597         protected double axisMin = Double.NaN;
598    // this symbol facilitates rendering of gridlines & axes
599         protected String tickLabelFontColor = DEFAULT_TICK_LABEL_FONT_COLOR;
600    // In CSS font-size pixels. These define the height of each
601    // character; our code relies on the rule of thumb that
602    // character width is approximately 3/5th this height to
603    // obtain a reasonably tight upper bound on tick label widths.
604         protected int tickLabelFontSize = DEFAULT_TICK_LABEL_FONTSIZE;
605         protected String tickLabelFontStyle = DEFAULT_TICK_LABEL_FONT_STYLE;
606         protected String tickLabelFontWeight = DEFAULT_TICK_LABEL_FONT_WEIGHT;
607    
608         protected String tickLabelFormat = DEFAULT_TICK_LABEL_FORMAT;
609         protected int tickLabelThickness = GChart.NAI;
610         protected int tickLabelPadding = 0;
611         protected int ticksPerLabel = 1;
612         protected int ticksPerGridline = 1;
613         protected int tickLength = DEFAULT_TICK_LENGTH;
614         
615         // this symbol facilitates rendering of labeled tick-marks
616         protected int tickThickness = DEFAULT_TICK_THICKNESS;
617             
618         // is axis itself visible (has no impact ticks or their labels)
619         boolean axisVisible = true;
620         
621         /**
622          * Adds a tick on this axis at the specified position.
623          * Note that explicitly adding a single tick via this method
624          * will eliminate any implicitly generated ticks associated with the
625          * <tt>setTickCount</tt> method.
626          * <p>
627          * The label associated with this tick will be generated by
628          * applying the format specified via <tt>setTickLabelFormat</tt>
629          * to the specified position.
630          * <p>
631          * This is a convenience method equivalent to
632          * <tt>addTick(tickPosition, thisAxis.formatAsTickLabel(tickPosition), GChart.NAI,
633          * GChart.NAI)</tt>. See {@link #addTick(double,String,int,int)
634          * addTick(tickPosition,tickLabel,widthUpperBound,heightUpperBound)}
635          * for details.
636          * 
637          * @param tickPosition the position, in model units,
638          *   along this axis at which this tick is displayed.
639          *   For example, if the axis range goes from 0 to 100,
640          *   a tick at position 50 would appear in the middle of
641          *   the axis.
642          *
643          * @see #clearTicks clearTicks
644          * @see #addTick(double,String) addTick(double,String)
645          * @see #addTick(double,String,int,int) addTick(double,String,int,int)
646          * @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
647          * @see #formatAsTickLabel formatAsTickLabel
648          * @see #setTickCount setTickCount
649          * @see #setTickLabelFormat setTickLabelFormat
650          * @see #setTickLabelFontStyle setTickLabelFontStyle
651          * @see #setTickLabelFontColor setTickLabelFontColor
652          * @see #setTickLabelFontWeight setTickLabelFontWeight
653          * @see #setTickLabelFontSize setTickLabelFontSize
654          * 
655          */
656         public void addTick(double tickPosition) {
657            addTick(tickPosition, formatAsTickLabel(tickPosition));  
658         }
659         // adds a labeled tick mark via this Axis' special system tick curve
660         private void addTickAsPoint(double tickPosition, String tickLabel,
661                                     Widget tickWidget, int widthUpperBound,
662                                     int heightUpperBound) {
663    
664           Curve c = getSystemCurve(ticksId);
665           if (isHorizontalAxis)  
666              c.addPoint(tickPosition, axisPosition*Double.MAX_VALUE);
667           else 
668              c.addPoint(axisPosition*Double.MAX_VALUE, tickPosition);
669    
670           // unlabeled tick--we are done, so return to save time
671           if (null == tickLabel && null == tickWidget)
672              return;
673           
674           //add an annotation representing the tick label
675           Curve.Point p = c.getPoint();
676           if (isHorizontalAxis) { 
677              // below tick on X, above it on (the future) X2
678              p.setAnnotationLocation(
679                (axisPosition < 0) ? AnnotationLocation.SOUTH :
680                                     AnnotationLocation.NORTH);
681              if (tickLabelPadding != 0) // padding < 0 is rare but allowed
682                 p.setAnnotationYShift(axisPosition*tickLabelPadding);
683              // else stick with default of 0 y-shift
684    
685           }
686           else {
687              // to left of tick mark on Y, to right of it on Y2
688              p.setAnnotationLocation(
689                   (axisPosition < 0) ? AnnotationLocation.WEST :
690                                        AnnotationLocation.EAST);
691              if (tickLabelPadding != 0) 
692                p.setAnnotationXShift(axisPosition*tickLabelPadding);
693              // else stick with default of 0 x-shift
694           }
695    
696           
697           if (null != tickLabel) 
698              p.setAnnotationText(tickLabel, widthUpperBound, heightUpperBound);
699           else if (null != tickWidget)
700              p.setAnnotationWidget(tickWidget, widthUpperBound, heightUpperBound);
701           
702           p.setAnnotationFontSize(getTickLabelFontSize());
703           p.setAnnotationFontStyle(getTickLabelFontStyle());
704           p.setAnnotationFontColor(getTickLabelFontColor());
705           p.setAnnotationFontWeight(getTickLabelFontWeight());
706    
707         }
708         /**
709          * Adds a tick at the specified position with the specified
710          * label on this axis, whose width and height are within
711          * the specified upper-bounds.
712          * 
713          * <p>
714          * Note that explicitly adding a single tick via this method
715          * will eliminate any auto-generated ticks associated with the
716          * <tt>setTickCount</tt> method. 
717          * 
718          * <p>
719          * Use this method to specify unusually spaced
720          * tick marks with labels that do not directly
721          * reflect the position (for example, for a logarithmic axis,
722          * or for a bar chart with special keyword-type labels, or
723          * a time axis that places date and time on two separate lines).
724          * 
725          * @param tickPosition the position, in model units, along
726          *   this axis at which the tick is displayed.
727          *   For example, if the axis range goes from 0 to 1,
728          *   a tick at position 0.5 would appear in the middle of
729          *   the axis.
730          *   
731          *  @param tickLabel the label for this tick.  HTML is
732          *  supported in tick labels, but it must be prefixed by
733          *  <tt>&lt;html&gt</tt>.  See the {@link
734          *  Curve.Point#setAnnotationText(String,int,int)
735          *  setAnnotationText} method for more information.
736          *
737          *  @param widthUpperBound an upper bound on the width of
738          *  the text or HTML, in pixels. Use <tt>GChart.NAI</tt> to
739          *  get GChart to estimate this width for you. See the
740          *  <tt>setAnnotationText</tt> method for more information.
741          * 
742          *  @param heightUpperBound an upper bound on the height of
743          *  the text or HTML, in pixels. Use <tt>GChart.NAI</tt> to
744          *  get GChart to estimate this height for you. See the
745          *  <tt>setAnnotationText</tt> method for more information.
746          *
747          * @see #clearTicks clearTicks
748          * @see #addTick(double) addTick(double)
749          * @see #addTick(double,String) addTick(double,String)
750          * @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
751          * @see #setTickCount setTickCount
752          * @see #setTickLabelFormat setTickLabelFormat
753          * @see #setTickLabelFontSize setTickLabelFontSize
754          * @see #setTickLabelFontStyle setTickLabelFontStyle
755          * @see #setTickLabelFontColor setTickLabelFontColor
756          * @see #setTickLabelFontWeight setTickLabelFontWeight
757          * @see Curve.Point#setAnnotationText(String,int,int)
758          *      setAnnotationText
759          * @see Curve.Point#setAnnotationWidget setAnnotationWidget
760          *      
761          */
762         public void addTick(double tickPosition, String tickLabel,
763                             int widthUpperBound,
764                             int heightUpperBound) {
765           chartDecorationsChanged = true;
766           if (GChart.NAI != tickCount) { // clear out any auto-generated ticks
767             Curve cTicks = getSystemCurve(ticksId);  
768             cTicks.clearPoints();
769             tickCount = GChart.NAI;
770           }
771           addTickAsPoint(tickPosition, tickLabel, null, widthUpperBound,
772                    heightUpperBound);       
773         }
774    
775         /**
776          * Adds a tick at the specified position with the specified
777          * label on this axis.
778          * <p>
779          *
780          * This is a convenience method equivalent to
781          * <tt>addTick(tickPosition, tickLabel, GChart.NAI,
782          * GChart.NAI)</tt>. Most applications can usually just
783          * use this convenience method. See {@link #addTick(double,String,int,int)
784          * addTick(tickPosition,tickLabel,
785          * widthUpperBound,heightUpperBound)} for the fine print.
786          *
787          * @param tickPosition the position, in model units, along
788          *   this axis at which the tick is displayed.
789          *   
790          * @param tickLabel the plain text or
791          * (<tt>&lt;html&gt</tt>-prefixed) HTML defining the tick's
792          * label.
793          *
794          * @see #addTick(double,String,int,int) addTick(double,String,int,int)
795          * @see #addTick(double,Widget) addTick(double,Widget)
796          * 
797          */ 
798         public void addTick(double tickPosition,
799                             String tickLabel) {
800              addTick(tickPosition, tickLabel, GChart.NAI, GChart.NAI);
801         }
802         
803         /**
804          *  Adds a widget-defined tick label at the specified
805          *  position, whose width and height are within
806          *  the specified upper-bounds.
807          * 
808          *
809          *  <p>
810          ** 
811          ** This method is similar to
812          ** <tt>addTick(double,String,int,int)</tt> except that it
813          ** uses a widget, rather than a string, to define the
814          ** tick's label. Although the string-based method is faster
815          ** on first chart rendering, and uses less memory, the
816          ** widget-based method allows you to change the label
817          ** independently of the chart--potentially bypassing (or
818          ** speeding up) expensive chart updates later on.
819          **
820          ** <p>
821          ** 
822          ** You might use a widget-based tick label to pop up a
823          ** dialog that allows the user to edit the parameters
824          ** defining the axis (min, max, etc.) whenever they click
825          ** on one of the tick labels on that axis, to define
826          ** hovertext that appears when the user mouses over
827          ** a tick label, to use images for your tick labels, etc. 
828          **
829          * @param tickPosition the position, in model units, along
830          *   this axis at which the tick is displayed.
831          *   For example, if the axis range goes from 0 to 1,
832          *   a tick at position 0.5 would appear in the middle of
833          *   the axis.
834          *   
835          *  @param tickWidget the label for this tick, as defined
836          *  by any GWT Widget.
837          *
838          *  @param widthUpperBound an upper bound on the width of
839          *  the widget, in pixels. If this and the next
840          *  parameter are omitted, GChart will use
841          *  <tt>DEFAULT_WIDGET_WIDTH_UPPERBOUND</tt>.
842          *  
843          *  @param heightUpperBound an upper bound on the height of
844          *  the widget, in pixels. If this and the previous
845          *  parameter are omitted, GChart will use <tt>
846          *  DEFAULT_WIDGET_HEIGHT_UPPERBOUND</tt>
847          *
848          *  @see #addTick(double,Widget) addTick(double,Widget) 
849          *  @see #addTick(double,String,int,int) addTick(double,String,int,int) 
850          *  @see Curve.Point#setAnnotationWidget setAnnotationWidget
851          *  @see #DEFAULT_WIDGET_WIDTH_UPPERBOUND DEFAULT_WIDGET_WIDTH_UPPERBOUND
852          *  @see #DEFAULT_WIDGET_HEIGHT_UPPERBOUND DEFAULT_WIDGET_HEIGHT_UPPERBOUND
853          **/ 
854         public void addTick(double tickPosition, 
855                             Widget tickWidget,
856                             int widthUpperBound,
857                             int heightUpperBound) {
858           chartDecorationsChanged = true;
859           if (GChart.NAI != tickCount) { // clear out any auto-generated ticks
860             Curve cTicks = getSystemCurve(ticksId);  
861             cTicks.clearPoints();
862             tickCount = GChart.NAI;
863           }
864           addTickAsPoint(tickPosition, null, tickWidget, widthUpperBound,
865                    heightUpperBound);
866            
867         }
868    
869         /**
870          *  Adds a Widget-defined tick label at the specified
871          *  position. Convenience method equivalent to
872          *  <tt>addTick(tickPosition, tickWidget,
873          *  DEFAULT_WIDGET_WIDTH_UPPERBOUND,
874          *  DEFAULT_WIDGET_HEIGHT_UPPERBOUND)</tt>.
875          *  
876          * @param tickPosition the position, in model units, along
877          *   this axis at which the tick is displayed.
878          *   For example, if the axis range goes from 0 to 1,
879          *   a tick at position 0.5 would appear in the middle of
880          *   the axis.
881          *   
882          * @param tickWidget the label for this tick, as defined
883          *  by any GWT Widget.
884          *  
885          * @see #addTick(double,Widget,int,int)
886          * addTick(double,Widget,int,int)
887          *  
888          */
889         public void addTick(double tickPosition,
890                             Widget tickWidget) {
891            addTick(tickPosition, tickWidget,
892                    DEFAULT_WIDGET_WIDTH_UPPERBOUND,
893                    DEFAULT_WIDGET_HEIGHT_UPPERBOUND);
894         }
895         /**
896          *
897          * Removes all ticks from this axis. Specifically,
898          * erases any ticks that were explicitly specified via
899          * <tt>addTick</tt>, and also sets the tick count to 0.
900          * <p>
901          * 
902          * @see #setTickCount setTickCount
903          * @see #addTick(double) addTick(double)
904          * @see #addTick(double,String) addTick(double,String)
905          * @see #addTick(double,String,int,int) addTick(double,String,int,int)
906          * @see #addTick(double,Widget) addTick(double,Widget)
907          * @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
908          * 
909          */
910         public void clearTicks() {
911            chartDecorationsChanged = true;
912            tickCount = GChart.NAI;
913            Curve c = getSystemCurve(ticksId);
914            c.clearPoints();
915         }
916    
917    
918         /**
919          * Converts a pixel, client-window coordinate position along this
920          * axis into the model units associated with this axis.
921          * <p>
922          *
923          * For example, if the client coordinate associated with 
924          * this axis' midpoint were passed to this method, it would return
925          * <tt>(getAxisMin() + getAxisMax())/2.0</tt>.
926          * <p>
927          *
928          * <small> Note that the client/model coordinate mapping used is as
929          * of the last <tt>update</tt>. Before the first <tt>update</tt>,
930          * this method returns <tt>GChart.NaN</tt>.  This method also
931          * invokes either <tt>getAbsoluteTop</tt> (for the y or y2 axis) or
932          * <tt>getAbsoluteLeft</tt> (for the x axis),
933          * and these GWT methods return 0 if the chart isn't actually
934          * rendered within the browser. So, results likely won't be useful
935          * to you until after the page containing your chart becomes
936          * visible to the user. Since most applications are expected to
937          * invoke this method in response to the user mousing over the
938          * page, these requirements should usually be satisfied.  </small>
939          * <p>
940          *
941          * <small> Saurabh Hirani in <a href=
942          * "http://groups.google.com/group/Google-Web-Toolkit/msg/80301715acb6f719">
943          * this GWT Forum post</a> and in GChart <a
944          * href="http://code.google.com/p/gchart/issues/detail?id=21">issue
945          * #22</a> most recently suggested the need for client to model
946          * coordinate mapping. Client to
947          * model conversion was requested earlier in GChart <a
948          * href="http://code.google.com/p/gchart/issues/detail?id=9">issue
949          * #9</a> from <a href="http://yoxel.com">yoxel.com</a>.
950          * </small>
951          *
952          * @param clientCoordinate a pixel-based coordinate that defines
953          * the dimension associated with this axis in the standard
954          * client window coordinates of GWT.
955          *
956          * @return the location defined by the client-coordinate argument,
957          * but converted into the model units associated
958          * with this axis.
959          *
960          * @see #getMouseCoordinate getMouseCoordinate
961          * @see #modelToClient modelToClient
962          * @see #pixelToModel pixelToModel
963          * @see #modelToPixel modelToPixel
964          * 
965          *
966          */
967         public abstract double clientToModel(int clientCoordinate);
968         
969         // these are used in formatting tick positions into tick labels:
970         private NumberFormat numberFormat =
971            NumberFormat.getFormat(DEFAULT_TICK_LABEL_FORMAT);
972         private DateTimeFormat dateFormat =
973            DateTimeFormat.getShortDateTimeFormat();
974         private final int NUMBER_FORMAT_TYPE = 0;
975         private final int DATE_FORMAT_TYPE = 1;
976         private final int LOG10INVERSE_FORMAT_TYPE = 2;
977         private final int LOG2INVERSE_FORMAT_TYPE = 3;
978         private int tickLabelFormatType = NUMBER_FORMAT_TYPE;
979         /**
980         * 
981         * Applies this axis' tick label format to format a given value.
982         * 
983         * @return the value formated as per this axis' currently specified 
984         * tick label format.
985         * 
986         * @see  #setTickLabelFormat(String) setTickLabelFormat
987         * 
988         */
989         public String formatAsTickLabel(double value) {
990           String result = null;
991           switch (tickLabelFormatType) {
992             case DATE_FORMAT_TYPE:
993               Date transDate = new Date((long) value);
994               result = dateFormat.format(transDate);
995               break;
996             case LOG10INVERSE_FORMAT_TYPE:
997               value = Math.pow(10., value);
998               result = numberFormat.format(value);
999               break;
1000             case LOG2INVERSE_FORMAT_TYPE:
1001               value = Math.pow(2., value);
1002               result = numberFormat.format(value);
1003               break;
1004             default:  
1005               result = numberFormat.format(value);
1006               break;
1007           }
1008           
1009           return result;
1010         }
1011         /**
1012          * @deprecated
1013          *
1014          * Equivalent to the better-named formatAsTickLabel.
1015    
1016          * <p>
1017          *
1018          * @see #formatAsTickLabel formatAsTickLabel
1019          * 
1020          */
1021         public String formatNumberAsTickLabel(double value) {
1022            return formatAsTickLabel(value);
1023         }
1024    
1025         /** Returns the previously specified label of this axis.
1026          **
1027          ** @return the Widget used as the label of this axis
1028          **
1029          ** @see #setAxisLabel setAxisLabel
1030          ** 
1031          */
1032         public Widget getAxisLabel() {
1033           return axisLabel;
1034         }
1035         
1036         /** Returns the thickness of the axis-label-holding region
1037          ** adjacent to the region allocated for this axis' tick labels.
1038          ** <p>
1039          **
1040          ** Note that if the axis label is <tt>null</tt> (the
1041          ** default) then this method always returns 0, since
1042          ** in that case no rectangular region will be allocated
1043          ** for the axis label.
1044          ** <p>
1045          ** 
1046          ** @return the thickness of the axis-label-holding
1047          ** region, in pixels.
1048          **
1049          ** @see #setAxisLabelThickness setAxisLabelThickness
1050          ** 
1051          */
1052         public int getAxisLabelThickness() {
1053            int result = 0;
1054    // Base class implementation is for y axes (x-axis will override). 
1055            final int EXTRA_CHARWIDTH = 2; // 1-char padding on each side
1056            final int DEF_CHARWIDTH = 1; // when widget has no text
1057            if (null == getAxisLabel())
1058               result = 0;
1059            else if (GChart.NAI != axisLabelThickness) 
1060               result = axisLabelThickness;
1061            else if (getAxisLabel() instanceof HasHTML) {
1062               int charWidth = htmlWidth(
1063                  ((HasHTML) (getAxisLabel())).getHTML());
1064               result = (int) Math.round((charWidth + EXTRA_CHARWIDTH) *
1065                          getTickLabelFontSize() *
1066                         TICK_CHARWIDTH_TO_FONTSIZE_LOWERBOUND);
1067            }
1068            else if (getAxisLabel() instanceof HasText) {
1069               String text = ((HasText) (getAxisLabel())).getText();
1070               result = (int) Math.round((EXTRA_CHARWIDTH + 
1071                          ((null==text)?0:text.length())) *
1072                          getTickLabelFontSize() *
1073                          TICK_CHARWIDTH_TO_FONTSIZE_LOWERBOUND);
1074            }
1075            else // non-text widget. Not a clue, just use def width
1076               result = (int) Math.round(
1077                        (DEF_CHARWIDTH + EXTRA_CHARWIDTH) *
1078                         getTickLabelFontSize() *
1079                         TICK_CHARWIDTH_TO_FONTSIZE_LOWERBOUND);
1080            return result;
1081         }
1082         /**
1083          ** Returns the maximum value displayed on this axis.
1084          ** If the explicitly specified maximum value is
1085          ** undefined (<tt>Double.NaN</tt>) the maximum value returned
1086          ** by this function is calculated as the maximum of
1087          ** all of the values either displayed on this axis via
1088          ** points on a curve, or explicitly specified via tick
1089          ** positions. 
1090          **
1091          ** @return maximum value visible on this axis, in
1092          ** "model units" (arbitrary, application-specific,
1093          ** units)
1094          **
1095          ** @see #setAxisMax setAxisMax
1096          ** @see #getDataMin getDataMin
1097          ** @see #getDataMax getDataMax
1098          **/ 
1099         public double getAxisMax() {
1100               
1101            if (!(axisMax!=axisMax)) { // x!=x is a faster isNaN
1102               return axisMax;
1103            }
1104            else if (GChart.NAI != tickCount) { 
1105              return getDataMax();
1106            }
1107            else {
1108               return Math.max(getDataMax(), getTickMax());           
1109            }
1110         }
1111         /**
1112          **
1113          ** Returns the minimum value displayed on this axis.
1114          ** If the minimum value is undefined (<tt>Double.NaN</tt>) the
1115          ** minimum value returned by this function is the
1116          ** minimum of all of the values either displayed on
1117          ** this axis via points on a curve, or explicitly specified
1118          ** via tick positions.
1119          **
1120          ** @return minimum value visible on this axis, in
1121          ** "model units" (arbitrary, application-specific,
1122          ** units)
1123          **
1124          ** @see #setAxisMin setAxisMin
1125          **/ 
1126         public double getAxisMin() {
1127            if (!(axisMin!=axisMin)) { // x!=x is a faster isNaN
1128               return axisMin; // explicitly set
1129            }
1130            else if (GChart.NAI != tickCount) { 
1131               return getDataMin();
1132            }
1133            else {  
1134               return Math.min(getDataMin(), getTickMin());           
1135            }
1136         }
1137    
1138       /** Is axis line visible on the chart? Note that
1139        ** this property only determines the visibility of the axis line
1140        ** itself. It does not control the visibility of the
1141        ** tick marks or tick labels along this axis.
1142        ** <p>
1143        **
1144        ** @return true if the axis line is visible, false otherwise.
1145        **
1146        ** @see #setAxisVisible setAxisVisible
1147        ** 
1148        **/ 
1149         public boolean getAxisVisible() {
1150           return axisVisible;
1151         }
1152    
1153    
1154         
1155         /** Returns the maximum data value associated with values
1156         ** represented on this axis. For example, for the left
1157         ** y-axis, this would be the largest y-value of all points
1158         ** contained in curves that are displayed on the left y-axis.
1159         ** 
1160         ** @return the maximum value associated with values
1161         **   mapped onto this axis.
1162         **
1163         ** @see #getDataMin getDataMin
1164         ** @see #getAxisMax getAxisMax
1165         ** @see #getAxisMin getAxisMin
1166         ** 
1167          */
1168        public abstract double getDataMax();
1169         /** Returns the minimum data value associated with values
1170         ** represented on this axis. For example, for the left
1171         ** y-axis, this would be the smallest y-value of all points
1172         ** contained in curves that are displayed on the left y-axis.
1173         **
1174         ** @return the minimum value associated with values
1175         **   mapped onto this axis.
1176         **
1177         ** @see #getDataMax getDataMax
1178         ** @see #getAxisMax getAxisMax
1179         ** @see #getAxisMin getAxisMax
1180         **
1181          */
1182        public abstract double getDataMin();
1183         
1184         /** Returns the gridline setting previously made with
1185          ** <tt>setHasGridlines</tt>.
1186          **
1187          ** @return true if gridlines have been enabled, false if not.
1188          **
1189          ** @see #setHasGridlines setHasGridlines
1190          ** 
1191          **/
1192         public boolean getHasGridlines() {
1193            return hasGridlines;
1194         }
1195    
1196         /**
1197          * Returns the coordinate along this axis that
1198          * is associated with the last "GChart-tracked" mouse
1199          * location.
1200          * <p>
1201          *
1202          * The coordinate returned is in the "scale" associated
1203          * with the axis. For example, if the axis mininum is
1204          * 0 and the maximum is 100, and the mouse is at the
1205          * axis midpoint, this method would return 50.
1206          * <p>
1207          *
1208          * The main intended use for this method is to allow you to create
1209          * points that, if they have x and y coordinates defined by calling
1210          * this method on appropriate axes, will be positioned on the chart
1211          * at the last GChart-tracked mouse location.
1212          * <p>
1213          *
1214          * As the user moves their mouse over the chart, GChart watches
1215          * those mouse moves and updates it's currently "tracked" mouse
1216          * location.  That internally maintained position is the basis for
1217          * the value returned by this method.  Note that the actual,
1218          * physical, mouse cursor position could differ from this 
1219          * GChart-tracked position because:
1220          * 
1221          * <p>
1222          *
1223          * <ol>
1224          * 
1225          *   <li>The mouse has moved off the chart, and it's
1226          *       GChart-tracked location has become undefined
1227          *       (this method returns <tt>Double.NaN</tt> in that case)
1228          *
1229          *   <li>You have invoked <tt>setHoverTouchingEnabled(false)</tt>
1230          *       which means that mouse moves are no longer tracked,
1231          *       so the last GChart-tracked mouse location will
1232          *       be the last position that the user clicked on.
1233          *
1234          *   <li>You have popped up a modal dialog that "eats" mouse
1235          *       moves so GChart no longer sees them. In that case,
1236          *       the GChart-tracked mouse location is the location the
1237          *       mouse was at when the modal dialog popped up.
1238          *
1239          *    <li>You are mousing over the opened hover widget (popup).
1240          *    Note that, to prevent the user from accidentally "touching"
1241          *    nearby points while interacting with the opened
1242          *    hover widget, GChart ignores mouse moves over
1243          *    the opened hover widget.
1244          *
1245          *    <li>Other, similar, reasons.
1246          *
1247          * </ol>
1248          * <p>
1249          *
1250          * In other words, this routine tells you where, for
1251          * hit testing and hover selection feedback purposes,
1252          * GChart considers the mouse to be, not the actual
1253          * physical location of the mouse. Despite the potential
1254          * for differences, in most cases, with the default
1255          * setting of <tt>setHoverTouchingEnabled(true)</tt>,
1256          * and when you are not over the opened hover widget,
1257          * you can use the value returned by this method
1258          * as if it represented the physical mouse location.
1259          * <p>
1260          * 
1261          * For an example that uses this method to create
1262          * points at the current mouse location within a very
1263          * simple line chart editor, see <a
1264          * href="package-summary.html#GChartExample22a">the
1265          * Chart Gallery's GChartExample22a</a>.  <p>
1266          *
1267          * 
1268          * @return the coordinate, projected along this axis, in
1269          *   the scale defined by this axis, representing the
1270          *   position GChart has currently "tracked" the mouse to,
1271          *   or <tt>Double.NaN</tt> if GChart has tracked the mouse
1272          *   right off the edge of the chart.
1273          *
1274          * @see #clientToModel clientToModel
1275          * @see #modelToClient modelToClient
1276          * @see #pixelToModel pixelToModel
1277          * @see #modelToPixel modelToPixel
1278          * @see GChart#setHoverTouchingEnabled setHoverTouchingEnabled
1279          *   
1280          */
1281         public abstract double getMouseCoordinate();
1282         /**
1283          * Returns the number of visible curves displayed on this axis.
1284          * <p>
1285          * 
1286          * @return the number of visible curves on this axis, or <tt>0</tt> if
1287          * there are no visible curves on this axis.
1288          *
1289          * @see Axis#setVisible setVisible
1290          * 
1291          */
1292         public int getNCurvesVisibleOnAxis() { return nCurvesVisibleOnAxis; }     
1293         /**
1294          ** Returns the number of ticks on this axis.
1295          **
1296          ** @return the number of ticks on this axis.
1297          **
1298          ** @see #setTickCount setTickCount
1299          ** @see #addTick(double) addTick(double)
1300          ** @see #addTick(double,String) addTick(double,String)
1301          ** @see #addTick(double,String,int,int) addTick(double,String,int,int)
1302          ** @see #addTick(double,Widget) addTick(double,Widget)
1303          ** @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
1304          ** @see #clearTicks clearTicks
1305          ** 
1306          **/
1307         public int getTickCount() {
1308            int result = tickCount;
1309            if (GChart.NAI == tickCount) { 
1310              Curve c = getSystemCurve(ticksId);
1311              result = c.getNPoints();
1312            }
1313            return result;
1314    
1315         }
1316         /**
1317          ** Returns the CSS font-weight specification to be used
1318          ** by this axis' tick labels.
1319          **
1320          ** @return font-weight of this axis' tick labels
1321          **
1322          ** @see #setTickLabelFontWeight setTickLabelFontWeight
1323          **/ 
1324         public String getTickLabelFontWeight() {
1325            return tickLabelFontWeight;
1326         }
1327         /**
1328          ** Returns the color of the font used to display the
1329          **    text of the tick labels on this axis.
1330          **   
1331          **   
1332          ** @return CSS color string defining the color of the text of
1333          **    the tick labels for this axis.
1334          **
1335          ** @see #setTickLabelFontColor setTickLabelFontColor
1336          **
1337          ** @see #DEFAULT_TICK_LABEL_FONT_COLOR DEFAULT_TICK_LABEL_FONT_COLOR
1338          **
1339          **
1340          ** 
1341          **/ 
1342         public String getTickLabelFontColor() {
1343            return tickLabelFontColor;
1344         }
1345    
1346         /**
1347          ** Returns the font-style of the font used to render tick
1348          ** labels on this axis (typically either "italic" or
1349          ** "normal") 
1350          **
1351          ** @return the CSS font-style in which tick labels of this axis
1352          **   are rendered.
1353          **
1354          ** @see #setTickLabelFontStyle setTickLabelFontStyle
1355          **/ 
1356         public String getTickLabelFontStyle() {
1357            return tickLabelFontStyle;
1358         }
1359         /** Returns the CSS font size, in pixels, used for tick labels
1360          ** on this axis.
1361          **
1362          ** @return the tick label font size in pixels
1363          **
1364          ** @see #setTickLabelFontSize setTickLabelFontSize
1365          **/ 
1366         public int getTickLabelFontSize() {
1367            return tickLabelFontSize;
1368         }
1369    
1370         /**
1371         ** Returns the tick label numeric format string for this
1372         ** axis.
1373         **
1374         ** @return numeric format used to generate tick labels.
1375         **
1376         ** @see #setTickLabelFormat setTickLabelFormat
1377         ** 
1378         **/ 
1379        public String getTickLabelFormat() {
1380           return tickLabelFormat;
1381        }
1382         /**
1383         ** Returns the amount of padding (blank space) between the
1384         ** ticks and their labels.<p>
1385         ** 
1386         ** @return amount of padding between ticks and their labels,
1387         ** in pixels.
1388         **
1389         ** @see #setTickLabelPadding setTickLabelPadding
1390         ** 
1391         **/ 
1392        public int getTickLabelPadding() {
1393           return tickLabelPadding;
1394        }
1395        // Does real work of public getTickLabelThickness; flag saves time
1396        // during repeated calls made in updateChartDecorations.
1397        int getTickLabelThickness(boolean needsPopulation) {
1398          int maxLength = 0;
1399          int result;
1400          if (tickLabelThickness != GChart.NAI)
1401            result = tickLabelThickness;
1402          else { // use an heuristic to estimate thickness            
1403           if (needsPopulation) maybePopulateTicks();
1404           Curve c = getSystemCurve(ticksId);
1405           int nTicks = c.getNPoints();
1406           for (int i=0; i < nTicks; i++) {
1407             String tt = c.getPoint(i).getAnnotationText();
1408             if (null != tt)
1409                maxLength = Math.max(maxLength,
1410                                     Annotation.getNumberOfCharsWide(tt));
1411           }
1412           result = (int) Math.round(maxLength * tickLabelFontSize *
1413                    TICK_CHARWIDTH_TO_FONTSIZE_LOWERBOUND);
1414          }
1415          return result;
1416        }
1417    
1418        /** Returns the thickness of the band adjacent to
1419         ** this axis that GChart will
1420         ** allocate to hold this axis' tick labels.
1421         ** <p>
1422         **
1423         ** @return width of band, in pixels, GChart will reserve
1424         **   for this axis' tick labels.
1425         **   
1426         ** @see #setTickLabelThickness setTickLabelThickness
1427         ** 
1428         **/ 
1429        public int getTickLabelThickness() {
1430           int result = getTickLabelThickness(true);
1431           return result;
1432        }   
1433    
1434        /**
1435         ** Returns the ratio of the number of ticks to the number of
1436         ** ticks that have an associated gridline. 
1437         **
1438         ** @return number of ticks per gridline for this axis
1439         **
1440         ** @see #setTicksPerGridline setTicksPerGridline
1441         **
1442         **/
1443        public int getTicksPerGridline() {
1444           return ticksPerGridline;
1445        }
1446        /**
1447         ** Returns the ratio of the number of ticks to the number of
1448         ** labeled ticks. 
1449         **
1450         ** @return number of ticks per label.
1451         **
1452         ** @see #setTicksPerLabel setTicksPerLabel
1453         **
1454         **/
1455        public int getTicksPerLabel() {
1456          return ticksPerLabel;
1457        }
1458    
1459         /**
1460         * Returns the length of ticks for this axis.
1461         *
1462         * @return the length of this axis' ticks, in pixels.
1463         *
1464         * @see #setTickLength setTickLength
1465         */
1466        public int getTickLength() {
1467           return tickLength;
1468        }
1469    
1470        // GChart adds a pixel to even, centered, tick lengths (only
1471        // odd-length HTML ticks can be exactly centered on 1px axis)
1472        int getActualTickLength() {
1473           int result = tickLength;
1474           if (TickLocation.CENTERED == tickLocation &&
1475               0 == (tickLength % 2)  && tickLength > 0)
1476              result++;
1477           return result;
1478        }
1479    
1480         /**
1481          * Returns relative location of ticks on this axis.
1482          * <p>
1483          * 
1484          * @see #setTickLocation setTickLocation
1485          *
1486          * @return <tt>TickLocation.INSIDE</tt>,
1487          *         <tt>TickLocation.OUTSIDE</tt>, or
1488          *         <tt>TickLocation.CENTERED</tt>
1489          *
1490          */
1491         public TickLocation getTickLocation() {
1492            return tickLocation;
1493         }
1494        
1495        
1496        /** Returns the amount of space along the axis reserved for
1497         *  the tick marks themselves, in pixels.
1498         *  <p>
1499         *
1500         *  This equals the length of
1501         *  the part of the tick that is outside of the plot area.
1502         *
1503         * @see #setTickLength setTickLength
1504         * @see #setTickLabelPadding setTickLabelPadding
1505         * @see #setTickLocation setTickLocation 
1506         * 
1507         * @return the space GChart will allocate just outside the
1508         * axis to hold any tick marks.
1509         * 
1510         */
1511        
1512        public int getTickSpace() {
1513           int result;
1514           if (TickLocation.CENTERED == tickLocation)
1515             result = (tickLength+1)/2;  // round up to nearest pixel
1516           else if (TickLocation.OUTSIDE == tickLocation)
1517              result = tickLength;
1518           else // INSIDE
1519              result = 0;
1520    
1521           return result;
1522        }
1523    
1524        
1525        /**
1526         * Returns the thickness of ticks for this axis.
1527         *
1528         * @return the thickness of this axis' ticks, in pixels.
1529         *
1530         * @see #setTickThickness setTickThickness
1531         * @see #getTickLength getTickLength
1532         */
1533        public int getTickThickness() {
1534           return tickThickness;
1535        }
1536    
1537    
1538         /**
1539          * Converts a coordinate position in the model units associated
1540          * with this axis into a corresponding coordinate position
1541          * expressed in standard GWT client-window pixel coordinates.
1542          * 
1543          * <p>
1544          *
1545          * For example, consider a completely undecorated chart (no axes,
1546          * tick labels, legend keys, etc.) that exactly fills a
1547          * 1000px wide client window, and whose x-axis min and max
1548          * are 0 and 100. Then <tt>getXAxis().modelToClient(50)</tt> would
1549          * return <tt>500</tt>.
1550          * <p>
1551          *
1552          * <small> Note that the client/model coordinate mapping used is as
1553          * of the last <tt>update</tt>. Before the first <tt>update</tt>,
1554          * this method returns <tt>GChart.NaN</tt>.  This method also
1555          * invokes either <tt>getAbsoluteTop</tt> (for the y or y2 axis) or
1556          * <tt>getAbsoluteLeft</tt> (for the x axis),
1557          * and these GWT methods return 0 if the chart isn't actually
1558          * rendered within the browser. So, results likely won't be useful
1559          * to you until after the page containing your chart becomes
1560          * visible to the user. Since most applications are expected to
1561          * invoke this method in response to the user mousing over the
1562          * page, these requirements should usually be satisfied.  </small>
1563          * 
1564          * @param modelCoordinate the position along this axis defined 
1565          *  in the model units associated with this axis.
1566          *
1567          * @return a pixel-based coordinate that defines
1568          * the position associated with the argument in the standard
1569          * pixel, client window, coordinates of GWT.
1570          *
1571          * @see #getMouseCoordinate getMouseCoordinate
1572          * @see #clientToModel clientToModel
1573          * @see #pixelToModel pixelToModel
1574          * @see #modelToPixel modelToPixel
1575          * 
1576          *
1577          */
1578         public abstract double modelToClient(double modelCoordinate);
1579    
1580         /**
1581          * Converts a coordinate position in the model units associated
1582          * with this axis into a corresponding coordinate position
1583          * expressed in GChart's decorated chart pixel coordinates.
1584          * <p>
1585          *
1586          * These
1587          * coordinates have their origin at the upper left corner
1588          * of the decorated GChart, and x pixel-coordinates that increase
1589          * as you move right, and y pixel-coordinates that increase
1590          * as you move down. They are related to GWT's standard
1591          * client window coordinates via the following equations:
1592          *
1593          * <pre>
1594          *   xClient = plotPanel.getAbsoluteLeft()
1595          *             - Window.getScrollLeft()
1596          *             + xPixel;
1597          *   yClient = plotPanel.getAbsoluteTop()
1598          *             - Window.getScrollTop()
1599          *             + yPixel;
1600          * </pre>
1601          * <p>
1602          *
1603          * In the above <tt>plotPanel</tt> is an internal
1604          * <tt>AbsolutePanel</tt>
1605          * GChart creates to hold the entire, decorated, chart. Apart from
1606          * borders and such applied to the GChart as a whole, its
1607          * absolute top and left positions should be the same as
1608          * those of the GChart itself.
1609          * <p>
1610          * 
1611          * <i>Tip:</i> In applications that continuously track mouse moves
1612          * over the chart, and where absolute and scroll positions cannot
1613          * change, you can gain a significant performance boost by
1614          * computing the difference between pixel and client coordinates
1615          * once (<tt>modelToPixel(axisMin)-modelToClient(axisMin)</tt>)
1616          * and then adding that difference to the client coordinates
1617          * to get the pixel coordinates, and then using
1618          * <tt>pixelToModel</tt>, instead of using <tt>clientToModel</tt>
1619          * directly, which must repeatedly call GWT's scroll and absolute
1620          * position methods.
1621          * 
1622          * <p>
1623          * 
1624          *
1625          * For example, for a completely undecorated chart (no tick labels,
1626          * legend keys, etc.) the plot area takes up the entire chart. In
1627          * that case, if the pixel units of the plot area range from
1628          * <tt>0...100</tt> along this axis, and the model coordinates range
1629          * from <tt>0...10</tt> along this axis, then
1630          * <tt>modelToPixel(modelCoordinate)</tt> returns
1631          * <tt>10*modelCoordinate</tt>.  <p>
1632          *
1633          * The model/pixel mapping is as of the last <tt>update</tt>;
1634          * this method returns <tt>Double.NaN</tt> before the first
1635          * <tt>update</tt>. Note that, unlike <tt>clientToModel</tt>
1636          * and <tt>modelToClient</tt>, the GChart does <i>not</i>
1637          * need to be actually rendered within the browser for you to
1638          * use this method--a call to update is sufficient.
1639          * <p>
1640          * 
1641          * <i>Tip:</i> If you need to access this mapping before
1642          * the first real update, you can explicitly specify the min and
1643          * max of this axis via <tt>setAxisMin</tt> and
1644          * <tt>setAxisMax</tt>, and then call <tt>update</tt> before adding
1645          * any curves to the chart (which, since the chart is empty, should
1646          * be very fast). This approach will allow you to convert between
1647          * model and pixel coordinates before the first real update, and
1648          * before the chart is rendered in the browser. 
1649          * <p>
1650          *
1651          * 
1652          *
1653          * @param modelCoordinate a position on this axis expressed 
1654          *  in the model units associated with this axis.
1655          *
1656          * @return the distance,
1657          * in pixels, from the left edge (for the x axis) or top
1658          * edge (for the y or y2 axis) of
1659          * the decorated chart to the given position on this axis.
1660          *
1661          * @see #getMouseCoordinate getMouseCoordinate
1662          * @see #clientToModel clientToModel
1663          * @see #modelToClient modelToClient
1664          * @see #modelToPlotAreaPixel modelToClient
1665          * @see #pixelToModel pixelToModel
1666          * 
1667          *
1668          */
1669         public abstract double modelToPixel(double modelCoordinate);
1670    
1671    
1672         /**
1673          * Converts a coordinate position in the model units associated
1674          * with this axis into a corresponding coordinate position
1675          * expressed in GChart's plot area pixel coordinates.
1676          * <p>
1677          *
1678          * These
1679          * coordinates have their origin at the upper left corner
1680          * of the plot area, and x pixel-coordinates that increase
1681          * as you move right, and y pixel-coordinates that increase
1682          * as you move down.
1683          * <p>
1684          *
1685          * The plot area is the rectangular region bounded by the
1686          * chart's axes, and with a size specified via
1687          * <tt>setChartSize</tt>, where the chart's curves are
1688          * typically displayed. 
1689          * <p>
1690          * 
1691          * Apart from a shift in the origin of the pixel coordinates
1692          * used, this method works just like <tt>modelToPixel</tt>;
1693          * see that method for additional details, tips, and
1694          * restrictions.
1695          *
1696          * @param modelCoordinate a position on this axis expressed 
1697          *  in the model units associated with this axis.
1698          *
1699          * @return the distance,
1700          * in pixels, from the left edge (for the x axis) or top
1701          * edge (for the y or y2 axis) of
1702          * the plot area to the given position on this axis.
1703          *
1704          * @see #getMouseCoordinate getMouseCoordinate
1705          * @see #plotAreaPixelToModel plotAreaPixelToModel
1706          * @see #modelToPixel modelToPixel
1707          * @see #setChartSize setChartSize
1708          *
1709          */
1710         public abstract double modelToPlotAreaPixel(double modelCoordinate);
1711    
1712         
1713         /**
1714          * Converts a coordinate position in GChart's decorated
1715          * chart pixel
1716          * coordinates into the model units associated with this axis.
1717          * <p>
1718          * 
1719          * GChart's decorated chart pixel
1720          * coordinates have their origin at the upper left corner
1721          * of the decorated GChart, and x pixel-coordinates that increase
1722          * as you move right, and y pixel-coordinates that increase
1723          * as you move down. They are related to GWT's standard
1724          * client window coordinates via the following equations:
1725          *
1726          * <pre>
1727          *   xClient = plotPanel.getAbsoluteLeft()
1728          *             - Window.getScrollLeft()
1729          *             + xPixel;
1730          *   yClient = plotPanel.getAbsoluteTop()
1731          *             - Window.getScrollTop()
1732          *             + yPixel;
1733          * </pre>
1734          * <p>
1735          *
1736          * 
1737          * In the above <tt>plotPanel</tt> is an internal
1738          * <tt>AbsolutePanel</tt>
1739          * GChart creates to hold the entire, decorated, chart. Apart from
1740          * borders and such applied to the GChart as a whole, its
1741          * absolute top and left positions should be the same as
1742          * those of the GChart itself.
1743          * <p>
1744          *
1745          * For example, for a completely undecorated chart (no tick labels,
1746          * legend keys, etc.) the plot area takes up the entire chart. In
1747          * that case, if the pixel units of the plot area range from
1748          * <tt>0...100</tt> along this axis, and the model coordinates range
1749          * from <tt>0...10</tt> along this axis, then
1750          * <tt>pixelToModel(pixelCoordinate)</tt> returns
1751          * <tt>pixelCoordinate/10.</tt>.  <p>
1752          *
1753          * The model/pixel mapping is as of the last <tt>update</tt>;
1754          * this method returns <tt>Double.NaN</tt> before the first
1755          * <tt>update</tt>. Note that, unlike <tt>clientToModel</tt>
1756          * and <tt>modelToClient</tt>, the GChart does <i>not</i>
1757          * need to be actually rendered within the browser for you to
1758          * use this method.
1759          * <p>
1760          * 
1761          * <i>Tip:</i> If you need to access this mapping before
1762          * the first real update, you can explicitly specify the min and
1763          * max of this axis via <tt>setAxisMin</tt> and
1764          * <tt>setAxisMax</tt>, and then call <tt>update</tt> before adding
1765          * any curves to the chart (which, since the chart is empty, should
1766          * be very fast). This approach will allow you to convert between
1767          * model and pixel coordinates before the first real update, and
1768          * before the chart is rendered in the browser. 
1769          * <p>
1770          *
1771          * @param pixelCoordinate the distance,
1772          * in pixels, from the left edge (for the x axis) or top
1773          * edge (for the y or y2 axis) of
1774          * the decorated chart to a point on this axis.
1775          *
1776          * @return that same position on this axis expressed in the
1777          *  the model units associated with this axis.
1778          *  
1779          * @see #getMouseCoordinate getMouseCoordinate
1780          * @see #clientToModel clientToModel
1781          * @see #modelToClient modelToClient
1782          * @see #modelToPixel modelToPixel
1783          * @see #plotAreaPixelToModel plotAreaPixelToModel
1784          *
1785          */
1786         public abstract double pixelToModel(int pixelCoordinate);
1787    
1788    
1789         /**
1790          * Converts a coordinate position in GChart's plot area
1791          * pixel
1792          * coordinates into the model units associated with this axis.
1793          * <p>
1794          * 
1795          * GChart's plot area pixel
1796          * coordinates have their origin at the upper left corner
1797          * of the plot area, and x pixel-coordinates that increase
1798          * as you move right, and y pixel-coordinates that increase
1799          * as you move down.
1800          * <p>
1801          *
1802          * The plot area is the rectangular region bounded by the
1803          * chart's axes, and with a size specified via
1804          * <tt>setChartSize</tt>, where the chart's curves are
1805          * typically displayed. 
1806          * <p>
1807          * 
1808          * Apart from a shift in the origin of the pixel coordinates
1809          * used, this method works just like <tt>pixelToModel</tt>;
1810          * see that method for additional details, tips, and
1811          * restrictions.
1812          *
1813          * @param pixelCoordinate the distance,
1814          * in pixels, from the left edge (for the x axis) or top
1815          * edge (for the y or y2 axis) of
1816          * the plot area to a point on this axis.
1817          *
1818          * @return that same position on this axis expressed in the
1819          *  the model units associated with this axis.
1820          *  
1821          * @see #modelToPlotAreaPixel modelToPlotAreaPixel
1822          * @see #pixelToModel pixelToModel
1823          * @see #setChartSize setChartSize
1824          *
1825          */
1826         public abstract double plotAreaPixelToModel(int pixelCoordinate);
1827    
1828    
1829         
1830       
1831         /** Specifies the label of this axis.
1832          ** <p>
1833          **
1834          ** This label will be positioned just outside of, and
1835          ** centered lengthwise on, the region adjacent to
1836          ** this axis that GChart reserves for this axis' tick labels.
1837          **
1838          ** @param axisLabel a Widget to use as the label of this axis.
1839          **
1840          ** @see #getAxisLabel getAxisLabel
1841          ** @see #setTickLabelThickness setTickLabelThickness
1842          ** @see #setAxisLabelThickness setAxisLabelThickness
1843          ** 
1844          */
1845         
1846         public void setAxisLabel(Widget axisLabel) {
1847            this.axisLabel = axisLabel;
1848            chartDecorationsChanged = true;
1849         }
1850         
1851         /**
1852          * Convenience method equivalent to
1853          * <tt>setAxisLabel(new HTML(html))</tt>
1854          *
1855          * @param html HTML text used to define the axis label
1856          * 
1857          * @see #setAxisLabel(Widget) setAxisLabel(Widget)
1858          */
1859          public void setAxisLabel(String html) {
1860           setAxisLabel(new HTML(html));
1861          }
1862    
1863         /** Sets the thickness of the axis-label-holding region
1864          ** adjacent to the region allocated for tick labels.<p>
1865          ** 
1866          ** The axis label widget will be centered in this region.
1867          ** Choose a thickness large enough to hold the largest
1868          ** font size you want users to be able to zoom up to
1869          ** without the axis label spilling over into
1870          ** adjacent regions.
1871          ** <p>
1872          **
1873          ** If the axis label thickness is <tt>GChart.NAI</tt> (the
1874          ** default), and the widget defining the axis label
1875          ** implements <tt>HasHTML</tt> (or <tt>HasText</tt>) then
1876          ** GChart uses a thickness based on the estimated number of
1877          ** non-tag characters in the first <tt>&lt;br&gt;</tt> or
1878          ** <tt>&lt;li&gt;</tt>
1879          ** delimited line for y-axis labels, and based on the
1880          ** estimated number of (<tt>&lt;br&gt;</tt> or
1881          ** <tt>&lt;li&gt;</tt> delimited)
1882          ** text lines for x-axis labels.<p>
1883          ** 
1884          ** Note that if the axis label is <tt>null</tt> (its
1885          ** default setting) then no space is allocated for the axis
1886          ** label, regardless of this thickness setting.
1887          ** <p>
1888          **
1889          ** @param thickness the thickness of the axis-label-holding
1890          ** region, in pixels, or <tt>GChart.NAI</tt> to use
1891          ** GChart's character-based default thickness estimates.
1892          **
1893          ** @see #getAxisLabelThickness getAxisLabelThickness
1894          ** @see #setAxisLabel setAxisLabel
1895          */
1896         public void setAxisLabelThickness(int thickness) {
1897           axisLabelThickness = thickness;
1898           chartDecorationsChanged = true;
1899         }
1900    
1901         /**
1902          ** Specifies the maximum value visible on this axis.
1903          ** <p>
1904          ** 
1905          ** Aspects of the chart rendered beyond this maximum will
1906          ** be clipped if the chart's <tt>clipToPlotArea</tt>
1907          ** property is <tt>true</tt>.
1908          ** 
1909          ** <p>
1910          ** 
1911          ** If <tt>Double.NaN</tt> is specified, this maximum
1912          ** is auto-determined as described in <tt>getAxisMax</tt>.
1913          ** 
1914          ** <p> <i>Performance tip:</i> Using auto-determined axis
1915          ** limits (via <tt>Double.NaN</tt>) forces GChart, at the
1916          ** next update, to re-render many chart elements whenever
1917          ** the min or max data value displayed on this axis
1918          ** changes.  These (often expensive) re-renderings can be
1919          ** avoided by using explicitly specified axis limits
1920          ** whenever possible. <p>
1921          **
1922          ** @param max maximum value visible on this axis, in "model units"
1923          ** (arbitrary, application-specific, units) or <tt>Double.NaN</tt>
1924          ** (the default value) to use an auto-determined maximum.
1925          **
1926          ** @see #getAxisMax getAxisMax
1927          ** @see #getDataMin getDataMin
1928          ** @see #getDataMax getDataMax
1929          ** @see GChart#setClipToPlotArea setClipToPlotArea
1930          ** 
1931          **/ 
1932         public void setAxisMax(double max) {
1933            chartDecorationsChanged = true;
1934            this.axisMax = max;
1935         }
1936         /**
1937          ** Specifies the minimum value of this axis.
1938          ** <p>
1939          ** 
1940          ** Aspects of the chart rendered at positions before this
1941          ** minimum
1942          ** value will be clipped if the chart's
1943          ** <tt>clipToPlotArea</tt> property is <tt>true</tt>.
1944          ** <p>
1945          ** 
1946          ** If <tt>Double.NaN</tt> is specified, this minimum
1947          ** is auto-determined as described in <tt>getAxisMin</tt>.
1948          ** 
1949          ** <p> <i>Performance tip:</i> Using auto-determined axis
1950          ** limits (via <tt>Double.NaN</tt>) forces GChart, at the
1951          ** next update, to re-render many chart elements whenever
1952          ** the min or max data value displayed on this axis
1953          ** changes.  These (often expensive) re-renderings can be
1954          ** avoided by using explicitly specified axis limits
1955          ** whenever possible. <p>
1956          ** 
1957          ** @param min minimum value visible on this axis, in "model units"
1958          ** (arbitrary, application-specific, units), or Double.NaN
1959          ** (the default) to use an auto-determined minimum.
1960          **
1961          ** @see #getAxisMin getAxisMin
1962          ** @see #getDataMin getDataMin
1963          ** @see #getDataMax getDataMax
1964          ** 
1965          **/ 
1966         public void setAxisMin(double min) {
1967    // min can change axis label width ==> changes position of plot area
1968            chartDecorationsChanged = true;
1969            this.axisMin = min;
1970         }
1971    
1972      /**
1973       ** Defines if this axis is visible. Note that
1974       ** this property only defines the visibility of the axis line
1975       ** itself, it does not control the visibility of
1976       ** tick marks or tick labels associated with the axis.
1977       ** 
1978       ** <p>
1979       ** <i>Tip:</i>Tick marks can be made invisible by using
1980       ** <tt>setTickThickness</tt> to set the tick thickness
1981       ** to 0. Tick labels can be made invisible by using
1982       ** <tt>setTickLabelFontColor</tt> to set the tick label
1983       ** color to the chart's background color.
1984       ** <p>
1985       ** 
1986       ** @param axisVisible false to hide axis, true to show it.
1987       **
1988       ** @see #setTickThickness setTickThickness
1989       ** @see #setTickLabelFontColor setTickLabelFontColor
1990       ** @see #getAxisVisible getAxisVisible
1991       **/ 
1992       public void setAxisVisible(boolean axisVisible) {
1993          chartDecorationsChanged = true;
1994          this.axisVisible = axisVisible;
1995       }   
1996         
1997         /**
1998          ** Specifies if this axis should have gridlines. When an
1999          ** axis has gridlines, tick marks with indexes <tt>0, N,
2000          ** 2*N,...</tt> where <tt>N</tt> is the value of this axis'
2001          ** <tt>ticksPerGridline</tt> property, are in effect
2002          ** extended across the entire chart.
2003          **
2004          ** @param hasGridlines true to display gridlines,
2005          ** false (the default) to not display them.
2006          **
2007          ** @see #getHasGridlines getHasGridlines
2008          ** @see #setTicksPerGridline setTicksPerGridline
2009          ** 
2010          **/ 
2011         public void setHasGridlines(boolean hasGridlines) {
2012            chartDecorationsChanged = true;
2013            this.hasGridlines = hasGridlines;
2014         }
2015         /** Sets the number of ticks to be placed on this axis. The
2016          ** default tick count is 10. Ticks are always evenly
2017          ** spaced across the entire axis, unless explicitly
2018          ** specified via <tt>addTick</tt>.
2019          ** <p>
2020          ** 
2021          ** Note that setting the tick count overrides (erases)
2022          ** any ticks explicitly specified via <tt>addTick</tt>.
2023          ** 
2024          ** @param tickCount the number of ticks for this axis. 
2025          ** 
2026          ** @see #getTickCount getTickCount
2027          ** @see #addTick(double) addTick(double)
2028          ** @see #addTick(double,String) addTick(double, String)
2029          ** @see #addTick(double,String,int,int) addTick(double,String,int,int)
2030          ** @see #addTick(double,Widget) addTick(double,Widget)
2031          ** @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
2032          ** @see #setTickLabelFormat setTickLabelFormat
2033          ** @see #setTickLabelFontSize setTickLabelFontSize
2034          ** @see #setTickLabelFontStyle setTickLabelFontStyle
2035          ** @see #setTickLabelFontColor setTickLabelFontColor
2036          ** @see #setTickLabelFontWeight setTickLabelFontWeight
2037          **
2038          **/
2039         public void setTickCount(int tickCount) {
2040            chartDecorationsChanged = true;
2041            getSystemCurve(ticksId).clearPoints(); // eliminate user specified ticks
2042            this.tickCount = tickCount;
2043         }
2044         /**
2045          ** Specifies the weight of the font used in this axis' tick
2046          ** labels.
2047          ** 
2048          ** @param cssWeight the weight of the font, such as bold,
2049          **    normal, light, 100, 200, ... 900, for tick labels.
2050          **
2051          ** @see #getTickLabelFontWeight getTickLabelFontWeight  
2052          ** @see #setTickLabelFormat setTickLabelFormat
2053          ** @see #setTickCount setTickCount
2054          ** @see #addTick(double) addTick(double)
2055          ** @see #addTick(double,String) addTick(double,String)
2056          ** @see #addTick(double,String,int,int) addTick(double,String,int,int)
2057          ** @see #addTick(double,Widget) addTick(double,Widget)
2058          ** @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
2059          ** @see #setTickLabelFontStyle setTickLabelFontStyle
2060          ** @see #setTickLabelFontColor setTickLabelFontColor
2061          ** @see #setTickLabelFontSize setTickLabelFontSize
2062          ** @see #DEFAULT_TICK_LABEL_FONT_WEIGHT DEFAULT_TICK_LABEL_FONT_WEIGHT
2063          **/ 
2064         public void setTickLabelFontWeight(String cssWeight) {
2065            chartDecorationsChanged = true;
2066            // assure that any existing ticks are updated with new weight
2067            Curve c = getSystemCurve(ticksId);
2068            int nPoints = c.getNPoints(); 
2069            for (int i = 0; i < nPoints; i++)
2070               c.getPoint(i).setAnnotationFontWeight(cssWeight);
2071            tickLabelFontWeight = cssWeight;
2072         }
2073         /**
2074          ** Specifies the color of the font used to render tick labels
2075          ** for this axis.
2076          ** 
2077          ** <p>
2078          ** For more information on standard CSS color
2079          ** specifications see the discussion in
2080          ** {@link Symbol#setBackgroundColor Symbol.setBackgroundColor}.
2081          ** <p>
2082          **        
2083          ** @param cssColor color of the font used to display this
2084          **    axis' tick labels, in standard CSS format.
2085          **
2086          ** @see #getTickLabelFontColor getTickLabelFontColor  
2087          ** @see #setTickLabelFormat setTickLabelFormat
2088          ** @see #setTickCount setTickCount
2089          ** @see #addTick(double) addTick(double)
2090          ** @see #addTick(double,String) addTick(double,String)
2091          ** @see #addTick(double,String,int,int) addTick(double,String,int,int)
2092          ** @see #addTick(double,Widget) addTick(double,Widget)
2093          ** @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
2094          ** @see #setTickLabelFontStyle setTickLabelFontStyle
2095          ** @see #setTickLabelFontWeight setTickLabelFontWeight
2096          ** @see #setTickLabelFontSize setTickLabelFontSize
2097          **/ 
2098         public void setTickLabelFontColor(String cssColor) {
2099            chartDecorationsChanged = true;
2100            Curve c = getSystemCurve(ticksId);
2101            int nPoints = c.getNPoints(); 
2102            for (int i = 0; i < nPoints; i++)
2103               c.getPoint(i).setAnnotationFontColor(cssColor);
2104            tickLabelFontColor = cssColor;
2105         }
2106    
2107         /**
2108          ** Specifies the CSS font-style of this
2109          ** axis' tick labels.
2110          **
2111          ** @param cssStyle any valid CSS font-style, namely,
2112          **   normal, italic, oblique, or inherit.
2113          **
2114          ** @see #getTickLabelFontStyle getTickLabelFontStyle  
2115          ** @see #setTickLabelFormat setTickLabelFormat
2116          ** @see #setTickCount setTickCount
2117          ** @see #addTick(double) addTick(double)
2118          ** @see #addTick(double,String) addTick(double,String)
2119          ** @see #addTick(double,String,int,int) addTick(double,String,int,int)
2120          ** @see #addTick(double,Widget) addTick(double,Widget)
2121          ** @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
2122          ** @see #setTickLabelFontColor setTickLabelFontColor
2123          ** @see #setTickLabelFontWeight setTickLabelFontWeight
2124          ** @see #setTickLabelFontSize setTickLabelFontSize
2125          ** @see #DEFAULT_TICK_LABEL_FONT_STYLE
2126          ** DEFAULT_TICK_LABEL_FONT_STYLE
2127          **/ 
2128         public void setTickLabelFontStyle(String cssStyle) {
2129            chartDecorationsChanged = true;
2130            Curve c = getSystemCurve(ticksId);
2131            int nPoints = c.getNPoints(); 
2132            for (int i = 0; i < nPoints; i++)
2133               c.getPoint(i).setAnnotationFontStyle(cssStyle);
2134            tickLabelFontStyle = cssStyle;
2135         }
2136    
2137         /**
2138          ** Sets the CSS font size for tick labels on this
2139          ** axis, in pixels.
2140          **
2141          ** @param tickLabelFontSize the font size of tick labels
2142          **   displayed on this axis.
2143          **
2144          ** @see #getTickLabelFontSize getTickLabelFontSize
2145          ** @see #setTickLabelFormat setTickLabelFormat
2146          ** @see #setTickCount setTickCount
2147          ** @see #addTick(double) addTick(double)
2148          ** @see #addTick(double,String) addTick(double,String)
2149          ** @see #addTick(double,String,int,int) addTick(double,String,int,int)
2150          ** @see #addTick(double,Widget) addTick(double,Widget)
2151          ** @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
2152          ** @see #setTickLabelFontStyle setTickLabelFontStyle
2153          ** @see #setTickLabelFontColor setTickLabelFontColor
2154          ** @see #setTickLabelFontWeight setTickLabelFontWeight
2155          ** @see GChart#DEFAULT_TICK_LABEL_FONTSIZE DEFAULT_TICK_LABEL_FONTSIZE
2156          ** 
2157          **/ 
2158    
2159         public void setTickLabelFontSize(int tickLabelFontSize) {
2160            chartDecorationsChanged = true;
2161            Curve c = getSystemCurve(ticksId);
2162            int nPoints = c.getNPoints(); 
2163            for (int i = 0; i < nPoints; i++)
2164               c.getPoint(i).setAnnotationFontSize(tickLabelFontSize);
2165            this.tickLabelFontSize = tickLabelFontSize;
2166         }
2167    
2168         /**
2169         * Specifies a format string to be used in
2170         * converting the numeric values associated with each
2171         * tick on this axis into tick labels.  This string must
2172         * follow the conventions of the number format patterns
2173         * used by the GWT <a
2174         * href="http://google-web-toolkit.googlecode.com/svn/javadoc/1.4/com/google/gwt/i18n/client/NumberFormat.html">
2175         * NumberFormat</a> class, <i>with three
2176         * exceptions:</i>
2177         * <p>
2178         * <ol>
2179         * 
2180         *  <li><i>Log10 inverse prefix</i>: If the string begins
2181         *  with the prefix <tt>=10^</tt> the value is replaced with
2182         *  <tt>pow(10.,value)</tt> and the so-transformed value is
2183         *  then formatted using the part of the format string that
2184         *  comes after this prefix, which must be a valid GWT 
2185         *  <tt>NumberFormat</tt> pattern (e.g. "##.##").
2186         *  <p>
2187         *  For an example of how to use this prefix to create a
2188         *  semi-log plot, see <a
2189         *  href="package-summary.html#GChartExample04">the
2190         *  Chart Gallery's GChartExample04</a>.
2191         *  <p>
2192         *
2193         *  <li><i>Log2 inverse prefix</i>: If the string begins with
2194         *  the prefix <tt>=2^</tt> the value is replaced with
2195         *  <tt>pow(2.,value)</tt> and the so-transformed value is
2196         *  then formatted using the part of the format string that
2197         *  comes after this prefix, which must be a valid GWT
2198         *  <tt>NumberFormat</tt> pattern.
2199         *  <p>
2200         *  
2201         *  <li><i>Date casting prefix</i>: If the string begins with
2202         *  the prefix <tt>=(Date)</tt> the value is replaced with
2203         *  <tt>new Date((long) value)</tt> and the so-transformed
2204         *  value is then formatted using the format string that
2205         *  comes after this prefix, which must be a valid GWT
2206         *  <a href="http://google-web-toolkit.googlecode.com/svn/javadoc/1.4/com/google/gwt/i18n/client/DateTimeFormat.html">
2207         *  DateTimeFormat</a>  pattern (e.g. "yyyy-MM-dd&nbsp;HH:mm").
2208         *  For the special case format string of <tt>"=(Date)"</tt>
2209         *  (just the date casting prefix) GChart uses the 
2210         *  <tt>DateTimeFormat</tt> returned by the
2211         *  <tt>DateTimeFormat.getShortDateTimeFormat</tt> method.  <p>
2212         *  
2213         *  Note that the values associated with this Axis must
2214         *  represent the number of milliseconds since January 1,
2215         *  1970 (in the GMT time zone) whenever this date
2216         *  casting prefix is used.  <p>
2217         *
2218         *  
2219         *  For example, if the x-axis tick label format were
2220         *  "=(Date)MMM-dd-yyyy HH", then, for a tick located at the
2221         *  x position of 0, the tick label would be "Jan-01-1970 00"
2222         *  (on a client in the GMT time zone) and for a tick located
2223         *  at the x position of 25*60*60*1000 (one day + one hour,
2224         *  in milliseconds) the tick label would be "Jan-02-1970 01"
2225         *  (again, on a GMT-based client). Results would be
2226         *  shifted appropriately on clients in different time zones.
2227         *  <p>
2228         *
2229         *  Note that if your chart is based on absolute, GMT-based,
2230         *  millisecond times then date labels will change when your
2231         *  chart is displayed on clients in different time zones.
2232         *  Sometimes, this is what you want. To keep the date labels
2233         *  the same in all time zones, convert date labels into Java
2234         *  <tt>Date</tt> objects in your client-side code, then use
2235         *  the <tt>Date.getTime</tt> method, also in your
2236         *  client-side code, to convert those dates into the
2237         *  millisecond values GChart requires.  The <a
2238         *  href="package-summary.html#GChartExample12"> Chart
2239         *  Gallery's GChartExample12</a> illustrates how to use this
2240         *  second approach to produce a time series chart whose
2241         *  date-time labels are the same in all time zones.
2242         *  
2243         *  <p>
2244         *  
2245         *  <blockquote><small>
2246         *  
2247         *  Ben Martin describes an alternative (and more flexible)
2248         *  approach to formatting time series tick labels in his <a
2249         *  href="http://www.linux.com/feature/132854">GChart
2250         *  tutorial</a>. Ben's article, along with Malcolm Gorman's
2251         *  related <a
2252         *  href="http://groups.google.com/group/Google-Web-Toolkit/msg/6125ce39fd2339ac">
2253         *  GWT forum post</a> were the origin of this date
2254         *  casting prefix. Thanks! </small></blockquote>
2255         *    
2256         * </ol>
2257         * <p>
2258         * 
2259         * 
2260         * <p> Though HTML text is not supported in the tick label
2261         * format string, you can change the size, weight, style, and
2262         * color of tick label text via the
2263         * <tt>setTickLabelFont*</tt> family of methods. You
2264         * <i>can</i> use HTML in tick labels (e.g. for a multi-line
2265         * x-axis label) but but only if you define each tick label
2266         * explicitly using the <tt>addTick</tt> method.
2267         * 
2268         * @param format an appropriately prefixed
2269         *   GWT <tt>NumberFormat</tt> compatible or
2270         *   GWT <tt>DateTimeFormat</tt> compatible format string that
2271         *   defines how to convert tick values into tick labels.
2272         *
2273         * @see #setTickCount setTickCount
2274         * @see #addTick(double) addTick(double)
2275         * @see #addTick(double,String) addTick(double,String)
2276         * @see #addTick(double,String,int,int) addTick(double,String,int,int)
2277         * @see #addTick(double,Widget) addTick(double,Widget)
2278         * @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
2279         * @see #setTickLabelFontSize setTickLabelFontSize
2280         * @see #setTickLabelFontStyle setTickLabelFontStyle
2281         * @see #setTickLabelFontColor setTickLabelFontColor
2282         * @see #setTickLabelFontWeight setTickLabelFontWeight
2283         * @see #getTickLabelFormat getTickLabelFormat
2284         */
2285         public void setTickLabelFormat(String format) {
2286           // interpret prefixes and create an appropriate formatter
2287           if (!tickLabelFormat.equals(format)) {
2288             chartDecorationsChanged = true;
2289             if (format.startsWith("=(Date)")) {
2290               String transFormat = format.substring("=(Date)".length());
2291               if (transFormat.equals("")) // so "=(Date)" works
2292                 dateFormat = DateTimeFormat.getShortDateTimeFormat();
2293               else // e.g. "=(Date)mm/dd/yy hh:mm"
2294                 dateFormat = DateTimeFormat.getFormat(transFormat);
2295               tickLabelFormatType = DATE_FORMAT_TYPE;
2296             }
2297             else if (format.startsWith("=10^")) {
2298               String transFormat = format.substring("=10^".length());
2299               numberFormat = NumberFormat.getFormat(transFormat);
2300               tickLabelFormatType = LOG10INVERSE_FORMAT_TYPE;
2301             }
2302             else if (format.startsWith("=2^")) {
2303               String transFormat = format.substring("=2^".length());
2304               numberFormat = NumberFormat.getFormat(transFormat);
2305               tickLabelFormatType = LOG2INVERSE_FORMAT_TYPE;
2306             }
2307             else {
2308               numberFormat = NumberFormat.getFormat(format);
2309               tickLabelFormatType = NUMBER_FORMAT_TYPE;
2310             }
2311           }
2312           // remember original format (for use with the getter)
2313           tickLabelFormat = format;
2314         }
2315    
2316         /** Specifies the number of pixels of padding (blank space)
2317          ** between the tick marks and their labels. <p>
2318          ** 
2319          ** With the default of <tt>0</tt>, each
2320          ** tick label is flush against its tick mark.
2321          **
2322          ** @param tickLabelPadding the amount of padding between
2323          ** tick labels and tick marks, in pixels.
2324          ** 
2325          ** 
2326          ** @see #getTickLabelPadding getTickLabelPadding
2327          ** @see #setTickLength setTickLength
2328          ** @see #setTickLocation setTickLocation
2329          ** 
2330          **/
2331         public void setTickLabelPadding(int tickLabelPadding) {
2332           chartDecorationsChanged = true;
2333           this.tickLabelPadding = tickLabelPadding;   
2334         }
2335         /** Specifies the thickness of the region adjacent to
2336          ** this axis that GChart will reserve for purposes of
2337          ** holding this axis' tick labels.  <p>
2338          ** <p>
2339          **
2340          ** For vertical axes, this represents the width of the
2341          ** widest tick label, for horizontal axes, this represents
2342          ** the height of highest tick label.
2343          ** <p>
2344          **
2345          ** 
2346          ** By default, this property has the special "undefined"
2347          ** value <tt>GChart.NAI</tt>. With this value, the
2348          ** companion method <tt>getTickLabelThickness</tt> uses an
2349          ** HTML-based heuristic to estimate the thickness.
2350          **
2351          ** 
2352          ** @see #getTickLabelThickness getTickLabelThickness
2353          ** @see #setTickLabelFontSize setTickLabelFontSize
2354          ** @see #setTickLocation setTickLocation
2355          ** @see #setTickLabelPadding setTickLabelPadding
2356          ** @see #setAxisLabel setAxisLabel
2357          ** @see GChart#NAI NAI
2358          ** 
2359          **/
2360         public void setTickLabelThickness(int tickLabelThickness) {
2361           chartDecorationsChanged = true;
2362           this.tickLabelThickness = tickLabelThickness;
2363         }
2364         /** Specifies the ratio of the number of tick marks on the
2365          ** axis, to the number of gridlines on the axis. 
2366          ** <p>
2367          ** 
2368          ** For example, with the default value of 1, every tick has
2369          ** an associated gridline, whereas with a
2370          ** <tt>ticksPerGridline</tt> setting of 2, only the first,
2371          ** third, fifth, etc. ticks have gridlines.  
2372          ** 
2373          ** <p>
2374          ** 
2375          ** This setting only has an impact when the axis' gridlines
2376          ** are turned on, that is, when this axis'
2377          ** <tt>getHasGridlines</tt> method returns true.
2378          **      
2379          ** @see #setHasGridlines setHasGridlines
2380          ** @see #setTickCount setTickCount
2381          ** @see #addTick(double) addTick(double)
2382          ** @see #addTick(double,String) addTick(double,String)
2383          ** @see #addTick(double,String,int,int) addTick(double,String,int,int)
2384          ** @see #addTick(double,Widget) addTick(double,Widget)
2385          ** @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
2386          **
2387          ** @param ticksPerGridline the number of ticks on this
2388          **   axis per "gridline-extended" tick.
2389          ** 
2390          **/ 
2391         public void setTicksPerGridline(int ticksPerGridline) {
2392           if (ticksPerGridline <= 0)
2393             throw new IllegalArgumentException("ticksPerGridline=" +
2394               ticksPerGridline + "; ticksPerGridline must be > 0");
2395           chartDecorationsChanged = true; 
2396           this.ticksPerGridline = ticksPerGridline;
2397         }
2398         /** Specifies the ratio of the number of tick marks on the
2399          ** axis, to the number of labeled tick marks on the axis.
2400          ** <p>
2401          ** 
2402          ** For example, with the default value of 1, every tick is
2403          ** labeled, whereas with a <tt>ticksPerLabel</tt> setting
2404          ** of 2, only the first, third, fifth, etc. ticks are
2405          ** labeled.
2406          ** 
2407          ** <p>
2408          ** 
2409          ** This setting is only used when tick labels
2410          ** are specified implicitly via <tt>setTickCount</tt>. It
2411          ** is ignored when tick positions and their labels are
2412          ** explicitly specified via <tt>addTick</tt>.
2413          **
2414          ** @see #setTickCount setTickCount
2415          ** @see #addTick(double) addTick(double)
2416          ** @see #addTick(double,String) addTick(double,String)
2417          ** @see #addTick(double,String,int,int) addTick(double,String,int,int)
2418          ** @see #addTick(double,Widget) addTick(double,Widget)
2419          ** @see #addTick(double,Widget,int,int) addTick(double,Widget,int,int)
2420          **
2421          ** @param ticksPerLabel the ratio of the number of ticks,
2422          **  to the number of labeled ticks.
2423          **
2424          **/ 
2425         public void setTicksPerLabel(int ticksPerLabel) {
2426           chartDecorationsChanged = true;
2427           if (ticksPerLabel <= 0)
2428               throw new IllegalArgumentException("ticksPerLabel=" +
2429               ticksPerLabel + "; ticksPerLabel must be > 0");
2430            this.ticksPerLabel = ticksPerLabel;
2431         }
2432    
2433         /**
2434         * Sets this axis' tick length. Set the tick length to zero to
2435         * eliminate the tick entirely.
2436         * <p>
2437         * 
2438         *
2439         * @param tickLength the length of the tick.
2440         *
2441         * @see #getTickLength getTickLength
2442         * @see #setTickThickness setTickThickness
2443         * @see #setTickLabelPadding setTickLabelPadding
2444         * @see #setTickLocation setTickLocation
2445         * 
2446         */
2447        abstract public void setTickLength(int tickLength);
2448    
2449    
2450         /**
2451          * Specifies the location of the tick marks relative to this
2452          * axis, namely, if tick marks are outside, inside, or
2453          * centered on this axis.
2454          * <p>
2455          *
2456          * @see #getTickLocation getTickLocation
2457          * @see #setTickThickness setTickThickness
2458          * @see #setTickLength setTickLength
2459          * @see #setTickLabelPadding setTickLabelPadding
2460          * 
2461          * @param tickLocation Specify either
2462          * <tt>TickLocation.INSIDE</tt>,
2463          * <tt>TickLocation.OUTSIDE</tt>, or
2464          * <tt>TickLocation.CENTERED</tt>
2465          *
2466          */
2467         public void setTickLocation(TickLocation tickLocation) {
2468            this.tickLocation = tickLocation;
2469            chartDecorationsChanged = true;
2470            GChart.Symbol sym = getSystemCurve(ticksId).getSymbol();
2471            if (isHorizontalAxis) {
2472              sym.setSymbolType(
2473                 tickLocation.getXAxisSymbolType(axisPosition));
2474              sym.setHeight(getActualTickLength());
2475            }
2476            else {
2477              sym.setSymbolType(
2478                 tickLocation.getYAxisSymbolType(axisPosition));
2479              sym.setWidth(getActualTickLength());
2480            }
2481         }
2482    
2483    
2484         /**
2485         * Sets this axis' tick thickness.
2486         * <p>
2487         *
2488         * @param tickThickness the thickness of the tick.
2489         *
2490         * @see #getTickThickness getTickThickness
2491         * @see #setTickLength setTickLength
2492         * @see #setTickLabelPadding setTickLabelPadding
2493         * @see #setTickLocation setTickLocation
2494         * 
2495         */
2496        abstract public void setTickThickness(int tickThickness);
2497        void maybePopulateTicks() {
2498           if (tickCount != GChart.NAI) populateTicks();
2499        }
2500    
2501        // fills in the tick positions when auto-generated.
2502        private void populateTicks() {
2503           getSystemCurve(ticksId).clearPoints();
2504    //TODO: It should be possible to control the visibility of each axis,
2505    // including ticks and tick labels, independent of the specifications of
2506    // the tick marks on that axis, and independent of if any curves are
2507    // mapped to that axis or not.  A setVisible(Boolean isVisible) as a
2508    // three-way, with null being the current, dependent, defaults, and
2509    // TRUE, FALSE explicitly making the entire axis, including tick marks
2510    // and labels visible or not without having to zero the tick count, add
2511    // dummy curve to the axis, etc. to control axis visibility is needed.
2512           if (XTICKS_ID == ticksId || // x, y ticks are drawn even
2513               YTICKS_ID == ticksId || // if no curves are on these axes
2514               0 < getNCurvesVisibleOnAxis()) {
2515              AxisLimits l = getAxisLimits();
2516              for (int i = 0; i < tickCount; i++) {
2517                 // linear interpolation between min and max
2518                 double position =(tickCount == 1) ? l.max : 
2519                                  (l.min * ((tickCount-1)-i) + i * l.max)/(tickCount-1.0);
2520                 addTickAsPoint(position,
2521                                (0 == i % ticksPerLabel) ?
2522                                formatAsTickLabel(position) : null, null,
2523                                GChart.NAI, GChart.NAI);
2524              }
2525           }
2526        }
2527    
2528    
2529        // fills in the gridlines; ticks are assumed already populated
2530        void populateGridlines() {
2531           Curve cTicks = getSystemCurve(ticksId);  
2532           Curve cGridlines = getSystemCurve(gridlinesId);
2533           cGridlines.clearPoints();
2534           int nTicks = cTicks.getNPoints();
2535           for (int iTick = 0; iTick < nTicks; iTick++) {
2536              if (hasGridlines && (iTick % ticksPerGridline) == 0) {
2537                 Curve.Point p = cTicks.getPoint(iTick);
2538                 cGridlines.addPoint(p.getX(), p.getY());
2539              }
2540           }
2541        }
2542    
2543        protected void getAxisLimits(AxisLimits result) {
2544           // so we get 1-unit changes between adjacent ticks
2545           final int DEFAULT_AXIS_RANGE = DEFAULT_TICK_COUNT-1;
2546           double min = getAxisMin();
2547           double max = getAxisMax();
2548           // Adjust min/max so that special cases, like one-point
2549           // charts, do not have axes that shrink down to a point,
2550           // which would create numerical and visual difficulties.
2551           if ((min!=min) && (max!=max)) { // x!=x is a faster isNaN
2552           // e.g. no data and no explicitly specified ticks
2553              min = 0;                                   
2554              max = min + DEFAULT_AXIS_RANGE;
2555           }
2556           else if ((min!=min) && !(max!=max)) { // x!=x is a faster isNaN
2557             // e.g. no data but only max explicitly set
2558              min = max - DEFAULT_AXIS_RANGE;
2559           }
2560           else if (!(min!=min) && (max!=max)) { // x!=x is a faster isNaN
2561             // e.g. no data but only min explicitly set
2562              max = min + DEFAULT_AXIS_RANGE;
2563           }
2564           else if (min == max) {
2565             // e.g one data point only, or they set min=max
2566              max = min + DEFAULT_AXIS_RANGE;
2567           }
2568           result.min = min;
2569           result.max = max;
2570        }
2571        AxisLimits getAxisLimits() {
2572           getAxisLimits(currentLimits);
2573           return currentLimits;
2574        }
2575           
2576        void rememberLimits() {
2577           getAxisLimits(previousLimits);
2578        }
2579        boolean limitsChanged() {
2580           boolean result = !getAxisLimits().equals(previousLimits);
2581           return result;
2582        }
2583        
2584    
2585        /* similar to getTickText, except for the tick position */
2586         private double getTickPosition(Curve c, int iTick) {
2587            double result;
2588            if (isHorizontalAxis)
2589               result = c.getPoint(iTick).getX();
2590            else
2591               result = c.getPoint(iTick).getY();
2592            return result;
2593         }
2594    
2595        // returns the largest, explicitly specified, tick position
2596         private double getTickMax() {
2597            double result = -Double.MAX_VALUE;
2598            Curve c = getSystemCurve(ticksId);
2599            int nTicks = c.getNPoints();
2600            for (int i = 0; i < nTicks; i++)
2601               result = Math.max(result, getTickPosition(c, i));
2602            return result;
2603         }
2604    
2605        // returns the smallest, explicitly specified, tick position
2606         private double getTickMin() {
2607            double result = Double.MAX_VALUE;
2608            Curve c = getSystemCurve(ticksId);
2609            int nTicks = c.getNPoints();
2610            for (int i = 0; i < nTicks; i++)
2611               result = Math.min(result, getTickPosition(c, i));
2612            return result;
2613         }
2614         
2615    
2616        // Same as max, except treats NaN/MAX_VALUE values as "not there"
2617        protected double maxIgnoreNaNAndMaxValue(double x1, double x2) {
2618           double result;
2619           if ((x1!=x1) ||
2620               Double.MAX_VALUE == x1 ||
2621               -Double.MAX_VALUE == x1) // x!=x is a faster isNaN
2622              result = x2;
2623           else if ((x2!=x2) ||
2624                    Double.MAX_VALUE == x2 ||
2625                    -Double.MAX_VALUE == x2)
2626              result = x1;
2627           else
2628              result = Math.max(x1, x2);
2629           return result;
2630        }
2631        // Same as Math.min, except treats NaN/MAX_VALUE values as "not there"
2632        protected double minIgnoreNaNAndMaxValue(double x1, double x2) {
2633           double result;
2634           if ((x1!=x1) ||
2635               Double.MAX_VALUE == x1 ||
2636               -Double.MAX_VALUE == x1 ) // x!=x is a faster isNaN
2637              result = x2;
2638           else if ((x2!=x2) ||
2639                    Double.MAX_VALUE == x2 ||
2640                    -Double.MAX_VALUE == x2)
2641              result = x1;
2642           else
2643              result = Math.min(x1, x2);
2644           return result;
2645        }
2646         // does a dummy set of any dynamically determined axis
2647         // limit, so, for update purposes, they are considered
2648         // to have changed.
2649         void invalidateDynamicAxisLimits() {
2650            // x!=x is a faster isNaN
2651            if ((axisMin!=axisMin)) setAxisMin(axisMin); 
2652             if ((axisMax!=axisMax)) setAxisMax(axisMax);
2653         }
2654    
2655    
2656            
2657      } // end of class Axis       
2658    
2659      // creates canvas Widgets GChart needs for *_CANVAS symbol types.
2660      private static GChartCanvasFactory canvasFactory = null;
2661      /**
2662       *
2663       * Tells GChart how to create the canvas widgets it needs
2664       * (specifically, widgets that implement GChart's
2665       * <tt>GChartCanvasLite</tt> interface) to render your
2666       * charts using an external vector graphics library.  <p>
2667       *
2668       * You must define a class that implements
2669       * <tt>GChartCanvasFactory</tt> and then pass an instance of that
2670       * class to this method, if you want to have the fast, crisply drawn
2671       * connecting lines, polygonal areas, and 2-D pie slices that only a
2672       * vector graphics library can provide.
2673       * <p>
2674       *
2675       * <small>
2676       * <i>Note:</i> If all of your charts only have
2677       * rectangular elements (e.g. bar charts) GChart will continue
2678       * to render those charts using HTML elements, even if a
2679       * canvas factory is provided. Thus, there is no point
2680       * to defining a canvas factory if all you use GChart for
2681       * is bar charts, scatter plots without connecting lines
2682       * between each point, banded-fill pie slices, and so on.
2683       * <p>
2684       * 
2685       * On the other hand, if you need continously connected lines,
2686       * solid-fill pie slices, or solid-fill area charts (all of which
2687       * will also need a <tt>setFillSpacing(0)</tt> to specify continuous
2688       * filling) you will gain substantial performance/quality
2689       * improvements if you add a canvas factory.  <p>
2690       * </small>
2691       *
2692       * In detail, to exploit browser-based vector graphics (Mozilla's
2693       * canvas, IE's VML, etc.)  rendering in your charts, you must:
2694       *  
2695       * <ol>
2696       *  
2697       *   <li>Import an external GWT canvas library, such
2698       *   as provided by the
2699       *   <a href="http://code.google.com/p/google-web-toolkit-incubator/">
2700    GWT incubator project's</a> <tt>GWTCanvas</tt> class, into your project.
2701       *   For example, our GChart test application uses
2702       *   <tt>GWTCanvas</tt>
2703       *   and imports it by adding this line to its
2704       *   <tt>.gwt.xml</tt> file:
2705       * <p>  
2706       * <pre>
2707       *   &lt;inherits name='com.google.gwt.widgetideas.GWTCanvas' /&gt;
2708       * </pre>
2709       *
2710       * To make this work, we also had to add the
2711       * <a href="http://code.google.com/p/google-web-toolkit-incubator/">
2712       * gwt-incubator.jar</a> file (which contains
2713       * <tt>GWTCanvas</tt>) to our build path via Eclipse's
2714       * "Configure Build Path..." command and to the libraries
2715       * listed in the
2716       * <tt>classpath=...</tt> line of our ant build script's
2717       * <tt>java</tt> task.
2718       * 
2719       * <p>
2720       *
2721       * <small>For more on the <tt>GWTCanvas</tt> widget, see the <a
2722       * href="http://code.google.com/p/google-web-toolkit-incubator/wiki/GWTCanvas">
2723       * GWTCanvas Wiki page</a> within the GWT
2724       * incubator site.</small>
2725       * 
2726       * <p>
2727       * 
2728       *   <li>Implement a class that extends <tt>Widget</tt> and
2729       *   implements the <tt>GChartCanvasLite</tt> interface that GChart
2730       *   expects. Again, in GChart's <tt>GWTCanvas</tt>-based test
2731       *   application we use:
2732       * 
2733       * {@code.sample ..\..\..\..\..\..\gcharttestapp\src\com\googlecode\gchart\gcharttestapp\client\GWTCanvasBasedCanvasLite.java} 
2734       *   
2735       *   <li>Create a <tt>GChartCanvasFactory</tt> class that has a
2736       *   single <tt>create</tt> method that returns new
2737       *   instances of your <tt>GChartCanvasLite</tt> Widget.
2738       *   GChart's test application uses:
2739       *
2740       *
2741       * {@code.sample ..\..\..\..\..\..\gcharttestapp\src\com\googlecode\gchart\gcharttestapp\client\GWTCanvasBasedCanvasFactory.java} 
2742       *
2743       *   <li>Finally pass an instance of that factory to
2744       *   GChart via a single invocation of
2745       *   <tt>setCanvasFactory</tt>:
2746       *
2747       *   <pre>
2748       *  static { 
2749       *    GChart.setCanvasFactory(new GWTCanvasBasedCanvasFactory());
2750       *  } 
2751       *   </pre>
2752       *  <p>
2753       *  
2754       *  <small>Because the above line essentially completes the definition
2755       *  of the GChart class (and thus should only be executed once per
2756       *  application) a good home for it is in a static initializer, as
2757       *  shown above. I recommend placing that initializer within your
2758       *  application's <tt>EntryPoint</tt> class.</small>
2759       * 
2760       * </ol>
2761       * 
2762       *  <p>
2763       *
2764       *
2765       * <i>Note:</i> To make things a bit simpler for you, the following
2766       * code combines steps 2, 3, and 4 into a single chunk of easily pasted
2767       * boilerplate:
2768       * 
2769       * <p>
2770       * <small><small>
2771       * {@code.sample
2772       * ..\..\..\..\..\..\gchartdemoapp\src\com\googlecode\gchart\gchartdemoapp\client\GChartDemoApp.java#1} 
2773       * {@code.sample
2774       * ..\..\..\..\..\..\gchartdemoapp\src\com\googlecode\gchart\gchartdemoapp\client\GChartDemoApp.java#2} 
2775       * </small></small>
2776       * 
2777       * <p>
2778       *
2779       * To see the above boilerplate within a working example application,
2780       * follow the link at the bottom of GChart's
2781       * <a href="http://gchart.googlecode.com/svn/trunk/live-demo/v2_6/com.googlecode.gchart.gchartdemoapp.GChartDemoApp/GChartDemoApp.html">
2782       * live demo page</a> to examine its <tt>EntryPoint</tt> class' code.
2783       * 
2784       * <p>
2785       * 
2786       *  <i>GChart's mixed canvas and HTML rendering</i>
2787       *
2788       *  <small><blockquote>
2789       *
2790       *  Even with an external canvas factory enabled, many aspects
2791       *  of your chart will remain HTML-rendered. In general, GChart
2792       *  only renders non-rectangular, "continuously filled" aspects
2793       *  of your chart using canvas. Other aspects, such as the bars
2794       *  on a bar chart, or the individual rectangular point markers
2795       *  on a line chart, as well as all chart text, will remain HTML
2796       *  rendered.
2797       *  <p>
2798       *
2799       *  The rendering mode (HTML-only or HTML+canvas) for each curve is
2800       *  defined by if a canvas factory is available and if that curve uses
2801       *  the special <tt>setFillSpacing(0)</tt> setting (meaning:
2802       *  "continuously filled"). So, to force any curve to be only HTML
2803       *  rendered, simply set this fill spacing to a value greater than
2804       *  <tt>0</tt>.  <p>
2805       *
2806       *  HTML rendering offers some features not available with canvas
2807       *  rendering.  For example, only HTML-rendered curves can overwrite
2808       *  the enclosing page without monopolizing mouse events within that
2809       *  curve's bounding rectangle, and only HTML-rendered curves can
2810       *  define their "fill" via an image. HTML rendered curves can
2811       *  sometimes even be more memory-efficent, provided the number of
2812       *  pixels greatly exceeds the number of HTML elements. And the HTML
2813       *  only rendering option provides a useful least common
2814       *  denominator/fall-back, supported by even the most obscure
2815       *  browsers.<p>
2816       *
2817       *  Note that the internal curves that GChart uses to render tick
2818       *  marks, gridlines, etc.  are never canvas rendered, because they
2819       *  only involve vertical or horizontal rectangles, which GChart
2820       *  always renders with HTML. 
2821       *  
2822       *  </blockquote></small>
2823       *  
2824       * <p>
2825       * 
2826       * Finally, if you are content with GChart's built-in HTML-based
2827       * rendering, or if your charts only use rectangular elements (e.g.
2828       * bar charts) you don't need to bother with any of the steps listed
2829       * above. Your charts will then only depend on the standard GWT
2830       * distribution and the 3,000 or so lines of pure GWT Java that
2831       * implement GChart.  <p>
2832       *
2833       * <small> <i>Important</i>: GChart only uses your external
2834       * canvas facility to draw a chart's non-rectangular aspects.
2835       * Given how GChart works, a curve can only have
2836       * non-rectangular aspects if <tt>setFillSpacing</tt> has been
2837       * set to <tt>0</tt> (which implies "continuous filling"), and
2838       * <tt>setFillThickness</tt> has been set to a value <tt>&gt;
2839       * 0</tt>. If you are not seeing crisp, canvas-rendered area,
2840       * line, or pie charts, be sure to check these two settings on
2841       * the curves in question.  </small>
2842       * 
2843       * @see GChartCanvasFactory GChartCanvasFactory
2844       * @see GChartCanvasLite GChartCanvasLite
2845       * @see #getCanvasFactory getCanvasFactory
2846       * @see GChart.Symbol#setFillSpacing setFillSpacing
2847       * @see GChart.Symbol#setFillThickness setFillThickness
2848       * 
2849       */
2850      public static void setCanvasFactory(GChartCanvasFactory factory) {
2851         canvasFactory = factory;
2852      }
2853    
2854      /**
2855       * Returns the GChart class' canvas factory, or <tt>null</tt>
2856       * if no canvas factory has been specified.
2857       * 
2858       * @return the previously specified canvas factory
2859       *
2860       * @see #setCanvasFactory setCanvasFactory
2861       * 
2862       */
2863      public static GChartCanvasFactory getCanvasFactory() {
2864         return canvasFactory;
2865      }
2866      
2867      /**
2868       * Represents a curve on a chart, which includes
2869       * information such as the x,y coordinates of each point,
2870       * the symbol used to represent points on the curve, etc.
2871       * <p>
2872       * To create a new curve, use the <tt>GChart.addCurve</tt>
2873       * method.
2874       *
2875       * @see GChart#addCurve() addCurve()
2876       *
2877       */
2878      public class Curve {
2879         private boolean isVisible = true;
2880         private String legendHTML = null;
2881         private ArrayList<Point> points = new ArrayList<Point>();
2882         // symbol defines how every point on this curve is rendered
2883         private Symbol symbol = new Symbol(this);
2884    
2885         private YAxisId yAxisId = Y_AXIS;
2886    
2887         private boolean isValidated = false;
2888         boolean isValidated() { return isValidated; }
2889    /*
2890     * TestGChart14d revealed that curves.indexOf(curve) could, due to its
2891     * sequential search, create a performance bug if the chart had
2892     * over 100 curves (e.g. the 160 pie chart slices/curves of TestGChart14d)
2893     * <p>
2894     * 
2895     * With a little extra bookkeeping during add/remove curve to call these
2896     * methods (and the extra int) this problem was corrected.
2897     *
2898     *
2899     */ 
2900         private int indexOf = GChart.NAI; 
2901         void incrementIndex() {indexOf++;}
2902         void decrementIndex() {indexOf--;}
2903         void clearIndex() {indexOf = GChart.NAI;}
2904         int getIndexOf() {return indexOf;}
2905    //     private void assertCurveNotRemoved() {
2906    //       if (indexOf == GChart.NAI)
2907    //          throw new IllegalStateException(
2908    //            "Removed curves should not be modified. " + 
2909    //            "You removed a curve, but retained a reference " + 
2910    //            "to that curve, and then tried to modify one of " +
2911    //            "its properties after you removed it."); 
2912    //     }
2913         
2914         /*
2915          * No public constructor because curves are always
2916          * contained within, and managed by, their containing
2917          * GChart via its addCurve, removeCurve, and related
2918          * methods.
2919          *
2920          */
2921         Curve(int indexOf) {
2922            super();
2923            this.indexOf = indexOf;
2924         }
2925         /**
2926         * Adds a new point to the curve, at the end of the current
2927         * list of points, with the specified
2928         * coordinates in model-units (arbitrary, application-specific,
2929         * units).
2930         * <p>
2931         *
2932         * GChart gives a special interpretation to the following values:
2933         * <p>
2934         * 
2935         * <ol>
2936         * 
2937         * <li> If <tt>-Double.MAX_VALUE</tt> is specified for either x or y,
2938         * the point acts as if it were placed at the minimum visible
2939         * x or y position within the plot area.
2940         * <p>
2941         * 
2942         * <li> Similarly, if <tt>Double.MAX_VALUE</tt> is specified for
2943         * either x or y, the point acts as if it were placed at the
2944         * maximum visible x or y position within the plot area.  <p>
2945         * 
2946         * <p>
2947         * <li>If <tt>Double.NaN</tt> is specified for either x or y, the
2948         * point is created, but it will not be visible in the
2949         * charting region.
2950         * 
2951         * <p>
2952         * <i>Tip:</i> Connecting lines to/from such 
2953         * <tt>Double.NaN</tt> points are elided, so you can use such a
2954         * point to create a break in an otherwise connected curve.
2955         *
2956         * </ol>
2957         * 
2958         * 
2959         * @param x the x-coordinate of the new point
2960         * @param y the y-coordinate of the new point
2961         *
2962         * @see #getPoint getPoint
2963         * @see #addPoint(int,double,double) addPoint(int,double,double)
2964         * @see #removePoint removePoint
2965         * @see #clearPoints clearPoints
2966         * @see #getNPoints getNPoints
2967         */
2968         public void addPoint(double x, double y) {
2969           invalidate(); 
2970           points.add(new Point(x, y));
2971        }
2972    
2973         /**
2974         * Adds a new point at the specified position in the point
2975         * sequence, increasing the indexes of existing points at or after
2976         * the specified position by 1.
2977         *
2978         * @param iPoint the position that the new point will occupy
2979         * @param x the x-coordinate of the new point (model units)
2980         * @param y the y-coordinate of the new point (model units) 
2981         *
2982         * @see #getPoint getPoint
2983         * @see #addPoint(double, double) addPoint(double,double)
2984         * @see #removePoint removePoint
2985         * @see #clearPoints clearPoints
2986         * @see #getNPoints getNPoints
2987         */  
2988         public void addPoint(int iPoint, double x, double y) {
2989            invalidate(); 
2990            points.add(iPoint, new Point(x, y));
2991        }
2992    
2993       /**
2994       * Removes every point this curve contains.
2995       *
2996       * @see Point Point
2997       * @see #getPoint getPoint
2998       * @see #addPoint(double, double) addPoint(double,double)
2999       * @see #addPoint(int,double,double) addPoint(int,double,double)
3000       * @see #removePoint removePoint
3001       * @see #getNPoints getNPoints
3002       */
3003       public void clearPoints() {
3004          if (this == getTouchedCurve()) 
3005             plotPanel.touch(null);
3006          invalidate(); 
3007          points.clear();
3008       }
3009    
3010    
3011      /*
3012       * Locates index of vertical or horizontal hit-testing band
3013       * that the given point appears in. The first and last
3014       * "pseudo-bands" are devoted to holding all points that fall
3015       * either to the left of or to the right of (or above or below
3016       * for horizontal banding) the first or last "normal" band
3017       * covering the decorated chart's containing "box".
3018       * <p>
3019       *
3020       * Note that some points just slightly off the right or bottom edge
3021       * may not end up in a pseudo-band, due to the fact that the chart
3022       * width (or height) need not be an even multiple of the (fixed) band
3023       * thickness (the "last band sticking out a bit" effect).
3024       * 
3025       */
3026       private int getBand(int iPoint, double bandThickness) {
3027          int result = GChart.NAI;
3028          SymbolType symType = getSymbol().getSymbolType();
3029          double xPx = symType.getCenterX(plotPanel, getSymbol(), iPoint);
3030          if (xPx!=xPx) return result; // NaN points not in any band
3031          double yPx = symType.getCenterY(
3032                          plotPanel, getSymbol(), iPoint, onY2());
3033          if (yPx!=yPx) return result; // NaN points not in any band
3034    
3035          // now, we've got a point with x,y values in some sort of band
3036          
3037          if (getSymbol().isHorizontallyBanded()) {
3038             if (yPx < 0)    
3039                result = 0; // off-chart point above chart
3040             else if (yPx >= (bandList.length-EXTRA_BANDS)*bandThickness)
3041                result = bandList.length-1; // off-chart point below chart
3042             else
3043                // inside a normal, chart-covering, band
3044                result = 1 + (int) Math.floor(yPx/bandThickness);
3045          }
3046          else { // vertically banded
3047             if (xPx < 0)
3048                result = 0;  // off-chart point to the left
3049             else if (xPx >= (bandList.length-EXTRA_BANDS)*bandThickness)
3050                result = bandList.length-1; // off-chart point to the right
3051             else
3052                // within one of the real bands covering the chart
3053                result = 1 + (int) Math.floor(xPx/bandThickness);
3054          }
3055          return result;
3056       }
3057       
3058       /*
3059        * Number of hit-test bands for this curve, for a given band
3060        * thickness. 
3061        *
3062        */ 
3063       private int EXTRA_BANDS = 2;   // far left, right (top, bottom) bands
3064       private int getNBands(double bandThickness) {
3065          int result = EXTRA_BANDS;
3066          if (getSymbol().isHorizontallyBanded()) 
3067            result += (int) Math.ceil(getYChartSize()/bandThickness);
3068          else 
3069            result += (int) Math.ceil(getXChartSize()/bandThickness);
3070          return result;
3071       }
3072    
3073       /*
3074        * Separates points on this curve into bins associated with
3075        * successive vertical (or horizontal) bands across the entire
3076        * decorated chart.
3077        * <p>
3078        * 
3079        * Because busy charts typically distribute points evenly
3080        * across the chart, by jumping to the appropriate band's
3081        * list, we can (usually) greatly accelerate worst case mouse
3082        * hit testing. And because the bin organizing step only
3083        * requires a single pass over all the points (and less than a
3084        * two int memory overhead per point) it should almost always
3085        * be a "good deal" performance-wise (compared to a simple
3086        * full point-list scan with every hit test).<p>
3087        *
3088        * Points are placed into bins based on the (pixel) position
3089        * of the x (or, with horizontal bands, y) at the center of
3090        * the rendered symbol.  We choose bin size to guarantee that
3091        * bins are at least as wide (or high, for horizontally banded
3092        * hit testing) as the rendered symbols on this curve. This
3093        * simplifies hit testing, since bins are big enough to assure
3094        * that a single symbol straddles at most two adjacent bands.
3095        * Exploits fact that all symbols on the same curve have the
3096        * same size, and that curves with many points on them tend to
3097        * have smaller sized symbols.
3098        * 
3099        * <p>
3100        *
3101        * Note that, for bin placement purposes, pie slices are
3102        * considered to have a "center" equal to the center of the
3103        * pie that contains them, and to have a width and height
3104        * equal to the diameter of the pie containing the slice
3105        * (the "worst-case slice").
3106        * 
3107        * <p>
3108        *
3109        * Also note that a symbol whose center is in the right side
3110        * of a vertical band may overlap into the following band, and
3111        * one in the left side may overlap the preceding band. Thus
3112        * during hit testing, we must check not only the lists of
3113        * points in the bands "touched" by the mouse-cursor-centered
3114        * brush, but also 1) the band to the immediate left of the
3115        * leftmost touched band, whenever a left-side sub-band of that band
3116        * is touched by the brush and 2) the band to the immediate
3117        * right of the rightmost touched band, whenever a right-side
3118        * sub-band of that band is touched. The thickness of these left
3119        * and right side sub-bands equals half the symbol width.
3120        * Expanding the brush a half-symbol width on either edge
3121        * is the easiest way to apply these rules. Exactly
3122        * analogous statements apply to horizontal bands.  <p>
3123        *
3124        * A minimum band size is enforced to prevent the number of
3125        * bands from growing too large with small symbols. Each
3126        * symbol type defines if vertical or horizontal banding is
3127        * more appropriate, or if brush shape should determine
3128        * banding strategy (as of this writing, only horizontal bar
3129        * symbol types require horizontal hit-test bands).  Exploits the
3130        * fact that all symbols have a fixed size for at least one of
3131        * their dimensions (for example, vertical bars have variable
3132        * height but fixed width).<p>
3133        *
3134        * Note: this method must be called after the curve is
3135        * rendered during an update(), to assure that hit-test-bins
3136        * are consistent with rendered curves, and ready for use
3137        * before the first mouse hit testing is done.
3138        * <p> 
3139        *
3140        * After running this method, points on this curve within a given
3141        * band can be enumerated as in the following code:
3142        * <p>
3143        * 
3144        * <pre>
3145        *   Point p = null;
3146        *   for (int iPoint = bandList[iBand];
3147        *        iPoint != GChart.NAI;
3148        *        iPoint = p.getINextInBand()) {
3149        *      p = getPoint(iPoint);
3150        *      // do something requiring points in a given band...
3151        *   }
3152        *
3153        * </pre>
3154        *
3155        */
3156       private int[] bandList = null; // index of first point in each band
3157       private double bandThickness = Double.NaN;
3158       void clearBandList() {bandList = null;}
3159       void bandSeparatePoints() {
3160         bandThickness =
3161            getSymbol().getSymbolType().getBandThickness(plotPanel,
3162                                                         getSymbol(),onY2());
3163         int nBands = getNBands(bandThickness); 
3164             
3165         if (bandList == null || bandList.length != nBands)
3166            bandList = new int[nBands];
3167         // else bandList already has required length, reuse it.
3168    
3169         // all bands contain NAI terminated, empty lists to start with
3170         for (int i = 0; i < bandList.length; i++)
3171           bandList[i] = GChart.NAI;  
3172    
3173         for (int iPoint = 0; iPoint < getNPoints(); iPoint++) {
3174           int iBand = getBand(iPoint, bandThickness);
3175           Point p = getPoint(iPoint);
3176           if (GChart.NAI == iBand) {
3177              // point isn't rendered at all, so isn't in a band (a next
3178              // link pointing to self means "I'm not in any band"). To let
3179              // us skip over these points quickly during rendering.
3180              p.setINextInBand(iPoint);
3181           }
3182           else {
3183         // Add point to front of list for whatever band it's in
3184         // (note that point order therefore gets reversed).
3185             p.setINextInBand(bandList[iBand]);
3186             bandList[iBand] = iPoint;
3187           }
3188           
3189         }
3190       }
3191    /*
3192     * Returns the index of the point on this curve whose rendered
3193     * symbol intersects a rectangle with the specified width and
3194     * height centered on the specified point (this rectangle is
3195     * typically a point selection "brush", centered on the mouse
3196     * cursor).  <p>
3197     *
3198     * In the event that more than one point's rendered symbol
3199     * intersects with the specified rectangle, the point whose
3200     * center is closest to the specified rectangle's center is
3201     * returned. In the event of a tie, the point with the largest
3202     * point index is returned. If no point "touches" the rectangle,
3203     * <tt>GChart.NAI</tt> is returned.  <p>
3204     *
3205     * Assumes/requires up-to-date <tt>bandList</tt> array and
3206     * related <tt>iNextInBand</tt> indexes (these get defined within
3207     * the <tt>bandSeparatePoints</tt> method).
3208     * 
3209     */
3210    
3211       int getClosestTouchingPoint(int xBrush, int yBrush) {
3212    
3213         int result = GChart.NAI;
3214         // ANCHOR_MOUSE symbol type curves not band separated/hit tested
3215         if (null == bandList) return result; 
3216         SymbolType symType = getSymbol().getSymbolType();
3217         double dBest = Double.MAX_VALUE; // closest touching pt's distance^2
3218      
3219         int iBandFirst; 
3220         int iBandLast;
3221    
3222         int brushWidth = symType.getBrushWidth(getSymbol());
3223    /*
3224     * In every tested browser EXCEPT FF3, we don't need the +1 below to
3225     * select a 1px tall, off-chart, symbol with a 1x1 px brush
3226     * (specifically, to select the leftmost vertical bar on TestGChart28).
3227     * The +1  below in effect adds 1 px to the height of the brush to
3228     * workaround this problem. 
3229     * 
3230     */ 
3231         int brushHeight = symType.getBrushHeight(getSymbol()) + 1; 
3232         AnnotationLocation brushLocation = symType.getBrushLocation(
3233            getSymbol());
3234         int nBands = bandList.length;
3235    
3236         // Determine range of bands touched by brush, taking into
3237         // account potential for symbols whose centers are in one
3238         // band to "stick out" into an adjacent band by half-band
3239         // thickening of either end of the brush. 
3240         //
3241         // Note that the 0th and (nBand-1)th bands represent
3242         // "pseudo-bands" that hold all points that fall to the left
3243         // or right (or above or below if horizontally banded) the
3244         // rectangle occupied by the decorated chart.  The tacit
3245         // assumption is that such completely off-the-chart points
3246         // are rare, so it's OK to bunch them up into just 2 bands.
3247         if (getSymbol().isHorizontallyBanded()) {
3248            // horizontal bars and some curves with "wider than high" brushes
3249           double top = brushLocation.getUpperLeftY(yBrush, brushHeight, 0);
3250           double bottom = top + brushHeight;
3251           top -= bandThickness/2.;
3252           bottom += bandThickness/2.; 
3253           iBandFirst = (int) Math.max(0, Math.min(nBands-1,1+Math.floor(
3254              top / bandThickness))); 
3255           iBandLast = (int) Math.max(0, Math.min(nBands-1, 1+Math.floor(
3256              bottom / bandThickness))); 
3257         }
3258         else { // vertical bars, some curves with "tall or square" brushes 
3259           double left = brushLocation.getUpperLeftX(xBrush, brushWidth, 0); 
3260           double right = left + brushWidth;
3261           left -= bandThickness/2.0;
3262           right += bandThickness/2.0;       
3263           iBandFirst = (int) Math.max(0, Math.min(nBands-1, 1+Math.floor(
3264              left / bandThickness))); 
3265           iBandLast = (int) Math.max(0, Math.min(nBands-1, 1+Math.floor(
3266              right / bandThickness))); 
3267         }
3268    
3269         // Every point whose symbol touches the brush must be in one
3270         // of these bands. Search them to find closest touching point.
3271         for (int iBand = iBandFirst; iBand <= iBandLast; iBand++) {
3272           Point p = null;
3273           for (int iPoint = bandList[iBand];
3274                iPoint != GChart.NAI;
3275                iPoint=p.getINextInBand()) {
3276             if (iPoint < 0 || iPoint >= getNPoints())
3277                 throw new IllegalStateException(
3278    "Inappropriately terminated band-point-list, GChart bug likely. " + 
3279    "iPoint=" + iPoint + " nPoints=" + getNPoints() +
3280    " iBand="+iBand+" iBandFirst="+iBandFirst+" iBandLast="+iBandLast +
3281    " xBrush="+xBrush+" yBrush="+yBrush+" brushWidth="+brushWidth +
3282    " brushHeight=" +brushHeight + " bandThickness=" + bandThickness);
3283             p  = getPoint(iPoint);
3284             if (symType.isIntersecting(plotPanel, getSymbol(), 
3285                                        iPoint, onY2(),
3286                                        xBrush, yBrush,
3287                                        brushWidth, brushHeight)) {
3288               // this point touches the brush (keep it if closest)  
3289               double xPoint = symType.getCenterX(plotPanel, 
3290                                             getSymbol(), iPoint);
3291               double yPoint = symType.getCenterY(plotPanel, 
3292                                             getSymbol(), iPoint, onY2());
3293               double dx = getSymbol().xScaleFactor*(xPoint-xBrush);
3294               double dy = getSymbol().yScaleFactor*(yPoint-yBrush);
3295               double d = dx*dx + dy*dy;
3296               if (d < dBest) {  
3297                 result = iPoint;
3298                 dBest = d;      
3299               }
3300               else if (d == dBest && iPoint > result) {
3301                  // in the case of ties, choose largest point index
3302                  // (highest "z-order" -- the one "on top")
3303                 result = iPoint;
3304                 dBest = d;      
3305               }
3306                  
3307             }
3308           }
3309         }
3310                
3311         return result;
3312    
3313       }
3314       
3315      /**
3316       * @deprecated
3317       *
3318       * This method is equivalent to: 
3319       * <p>
3320       *<tt>getSymbol().getHovertextTemplate()</tt>
3321       * <p>
3322       * 
3323       * It is retained only for GChart 1.1 compatibility purposes.
3324       * 
3325       * @see Symbol#getHovertextTemplate() Symbol.getHovertextTemplate
3326       *
3327       */ 
3328        public String getHovertextTemplate() {
3329          return symbol.getHovertextTemplate();
3330        }
3331    
3332      /**
3333       ** Returns the HTML defining this curve's legend label.
3334       **
3335       ** @return the legend label HTML for this curve
3336       **
3337       ** @see #setLegendLabel setLegendLabel
3338       **
3339       **/ 
3340        public String getLegendLabel() {
3341          return legendHTML;
3342        }
3343      
3344      /**
3345       * Returns the number of points this curve contains.
3346       *
3347       * @return the number of points this curve contains.
3348       * 
3349       * @see #getPoint getPoint
3350       * @see #addPoint(double, double) addPoint(double,double)
3351       * @see #addPoint(int,double,double) addPoint(int,double,double)
3352       * @see #removePoint removePoint
3353       * @see #clearPoints clearPoints
3354       */  
3355        public int getNPoints() {
3356          return points.size();
3357        }
3358    
3359        /**
3360         * Returns a reference to the GChart that contains this
3361         * curve.
3362         *
3363         * @return GChart that contains this curve--its "parent".
3364         *
3365         */
3366        public GChart getParent() {return GChart.this; }
3367      
3368      /**
3369       * Convenience method equivalent to <tt>getPoint(getNPoints()-1)</tt>.
3370       * <p>
3371       * This method makes code more readable for the common case when
3372       * you first add a point to the end of a curve, and then modify that
3373       * point's attributes, as illustrated below:
3374       * <p>
3375       * <pre>
3376       *   class MyChart extends GChart {
3377       *     public MyChart() {
3378       *       addCurve();
3379       *       for (int i=0; i < 10; i++) {
3380       *         getCurve().addPoint(i,i);
3381       *         getCurve().getPoint().setAnnotationText("Point " + i);
3382       *       }
3383       *       update();
3384       *     }
3385       *   }
3386       * </pre>
3387       * 
3388       * @return the point on the curve with the highest integer index
3389       *
3390       * @see #getPoint(int) getPoint(int)
3391       * @see #getNPoints() getNPoints()
3392       * 
3393       */
3394        public Point getPoint() {
3395           Point result = getPoint(getNPoints()-1);
3396           return result;
3397        }
3398    
3399      /**
3400       * Returns a reference to the point at the specified
3401       * index.  The returned reference can be used to modify
3402       * various properties of the point, such as
3403       * its optional annotation (text label).
3404       * 
3405       * <p>
3406       * @param iPoint the index of the point to be returned.
3407       * @return a reference to the Point at the specified index.
3408       *
3409       * @see #addPoint(double, double) addPoint(double,double)
3410       * @see #addPoint(int,double,double) addPoint(int,double,double)
3411       * @see #removePoint removePoint
3412       * @see #clearPoints clearPoints
3413       * @see #getNPoints getNPoints
3414       */
3415        public Point getPoint(int iPoint) {
3416           if (iPoint < 0 || iPoint >= points.size())
3417              throw new IllegalArgumentException(
3418               "Point index iPoint=" + iPoint + ". " + 
3419               "is either < 0 or >= the number of points on the curve.");
3420           Point result = points.get(iPoint);
3421           return result;
3422        }
3423    
3424        
3425        /**
3426         * Returns the positional index (within this curve's list of
3427         * points) of the specified point.
3428         * <p>
3429         * 
3430         * Returns <tt>GChart.NAI</tt> if the specified point is not found on
3431         * this curve's point list.
3432         * 
3433         * <p>
3434         * @param point point whose list position is to be retrieved
3435         * @return position of point on this curve's point list, or
3436         *        <tt>GChart.NAI</tt>
3437         *        if not on the list.
3438         *
3439         * @see #getPoint() getPoint()
3440         * @see #getPoint(int) getPoint(int)
3441         * @see #addPoint addPoint
3442         * @see #removePoint removePoint
3443         * @see #clearPoints clearPoints
3444         * @see #getNPoints getNPoints
3445         */
3446        public int getPointIndex(Point point) {
3447           int result = points.indexOf(point);
3448           if (-1 == result) result = GChart.NAI;
3449           return result;
3450        }
3451      /**
3452        ** Returns the symbol associated with this curve.
3453        ** <p>
3454        ** 
3455        ** Though you cannot set the symbol itself (there is no
3456        ** <tt>setSymbol</tt> method) you can have essentially
3457        ** the same effect by setting the <tt>SymbolType</tt> (to get
3458        ** qualitatively different kinds of symbols, e.g.
3459        ** bar-chart bars vs. boxes) and by changing symbol
3460        ** attributes such as background color, height, and
3461        ** width.
3462        ** 
3463        ** @return the symbol used to represent points on this curve
3464        **
3465        ** @see Symbol#setSymbolType Symbol.setSymbolType
3466        ** @see Symbol#setBackgroundColor Symbol.setBackgroundColor
3467        ** @see Symbol#setBorderWidth Symbol.setBorderWidth
3468        ** @see Symbol#setBorderStyle Symbol.setBorderStyle
3469        ** @see Symbol#setWidth Symbol.setWidth
3470        ** @see Symbol#setHeight Symbol.setHeight
3471        ** @see Symbol#setModelWidth Symbol.setModelWidth
3472        ** @see Symbol#setModelHeight Symbol.setModelHeight
3473        ** 
3474        **/
3475         public Symbol getSymbol() {
3476           return symbol;
3477         }
3478      
3479      /**
3480       * Returns the y-axis (Y_AXIS or Y2_AXIS) this curve is
3481       * plotted on.
3482       *
3483       * @return an identifier, either Y_AXIS, or Y2_AXIS, indicating
3484       *   if this curve is plotted on the left (y) or right (y2) y-axis
3485       * 
3486       ** @see #setYAxis setYAxis
3487       ** @see GChart#Y_AXIS Y_AXIS
3488       ** @see GChart#Y2_AXIS Y2_AXIS
3489       ** 
3490       */
3491        public YAxisId getYAxis() {
3492          return yAxisId;
3493        }
3494    
3495      /** Is this curve visible on the chart and legend key,
3496        ** or is it hidden from view.
3497        **
3498        ** @return true if the curve is visible, false otherwise.
3499        **
3500        ** @see #setVisible setVisible
3501        **/ 
3502         public boolean isVisible() {return isVisible;}
3503    
3504      /** Convenience method equivalent to <tt>getYAxis()==Y2_AXIS</tt>.
3505       *
3506       * @return true if curve is on second y-axis, else false
3507       *
3508       * @see #getYAxis getYAxis
3509       */ 
3510        public boolean onY2() {
3511           return yAxisId == Y2_AXIS;
3512        }
3513      /**
3514       * Removes the point at the specified index.
3515       *
3516       * @param iPoint index of point to be removed.
3517       * 
3518       * @see #getPoint getPoint
3519       * @see #addPoint(double, double) addPoint(double,double)
3520       * @see #addPoint(int,double,double) addPoint(int,double,double)
3521       * @see #clearPoints clearPoints
3522       * @see #getNPoints getNPoints
3523       */
3524        public void removePoint(int iPoint) {
3525           if (iPoint < 0 || iPoint >= getNPoints())
3526              throw new IllegalArgumentException(
3527                 "iPoint=" + iPoint + " iPoint arg must be >= 0 and < " + 
3528                  getNPoints() + ", the number of points on the curve.");
3529           invalidate(); 
3530    
3531           // simulate user moving away from point before it is deleted
3532           // (this assures that any required hoverCleanup gets called,
3533           //  and clears the otherwise dangling reference to the point)
3534           if (plotPanel.touchedPoint == getPoint(iPoint))
3535              plotPanel.touch(null);  
3536           
3537           points.remove(iPoint);
3538        }
3539    
3540        /**
3541         * Removes the given point from this curve.
3542         * <p>
3543         *
3544         * If the given point is not on this curve, or is
3545         * <tt>null</tt>, an exception is thrown.
3546         *
3547         * @param p the point to be removed. 
3548         *
3549         *
3550         */
3551        public void removePoint(Point p) {
3552           if (null == p)
3553              throw new IllegalArgumentException("p cannot be null.");
3554           int index = getPointIndex(p);
3555           if (GChart.NAI == index)
3556              throw new IllegalArgumentException("p must be a point on this curve " +
3557                "(whose curveIndex is " + getParent().getCurveIndex(this) + ")");
3558           removePoint(index);
3559        }
3560      /**
3561       ** @deprecated
3562       **
3563       ** This method is equivalent to:
3564       ** <p>
3565       **  <tt>getSymbol().setHovertextTemplate(hovertextTemplate)</tt>
3566       ** <p>
3567       ** It is retained only for GChart 1.1 compatibility purposes.
3568       **
3569       ** @see Symbol#setHovertextTemplate Symbol.setHovertextTemplate
3570       **/ 
3571        public void setHovertextTemplate(String hovertextTemplate) {
3572           symbol.setHovertextTemplate(hovertextTemplate);
3573        }
3574      /**
3575       ** Sets the HTML that defines the label shown to the
3576       ** right of the icon representing the curve's symbol in
3577       ** the chart's legend.
3578       ** 
3579       ** <p>
3580       ** Setting the legend label to <tt>null</tt> removes the
3581       ** entire row (the label and the icon) associated with
3582       ** this curve from the chart key. 
3583       ** <p>
3584       ** Note that, since <tt>null</tt> is the default, unless
3585       ** you set at least one legend label, no chart key will
3586       ** appear at all.
3587       ** 
3588       ** @param legendHTML the HTML defining this curve's legend label.
3589       **                   or <tt>null</tt> to remove the curve from
3590       **                   the legend entirely.
3591       **                   
3592       ** @see #getLegendLabel getLegendLabel
3593       ** @see GChart#setLegendThickness setLegendThickness
3594       ** 
3595       **/ 
3596        public void setLegendLabel(String legendHTML) {
3597           chartDecorationsChanged = true;
3598           this.legendHTML = legendHTML;
3599        }
3600      /**
3601        ** Defines if this curve is visible both in the plotting
3602        ** region and on the legend key.
3603        ** <p>
3604        ** 
3605        ** <i>Notes:</i>
3606        **
3607        ** <ol>
3608        **  <li>A curve must also have a non-<tt>null</tt> legend label
3609        ** if it is to appear on the legend key.
3610        ** <p>
3611        ** 
3612        **  <li>Hidden curves are excluded from the computation
3613        ** of any auto-computed axis limits.
3614        **
3615        ** </ol>
3616        **
3617        ** @param isVisible false to hide curve, true to reveal it.
3618        **
3619        ** @see #isVisible() isVisible
3620        ** @see #setLegendLabel setLegendLabel
3621        ** 
3622        **/ 
3623        public void setVisible(boolean isVisible) {
3624           /* Axis curve count bookkeeping requires that curve be on the
3625            * list of curves.
3626            * <p>
3627            * 
3628            * Developer refs to removed curves could screw up this
3629            * bookkeeping.
3630            * <p>
3631            * 
3632            * Though often this would be a developer error, best not to
3633            * throw an exception because some developers could use deleted
3634            * curves as repositories for curve state data, or an event
3635            * sequence might produce setVisible calls through a dangling
3636            * curve reference after a curve had been removed, and best to
3637            * let developer get away with that kind of thing.  <p>
3638            * 
3639            * No point in invalidating since removed curves can never
3640            * become part of a rendered GChart again.
3641            * 
3642            */
3643           
3644            if (getIndexOf() == GChart.NAI) {
3645               this.isVisible = isVisible;
3646               return;          
3647            }
3648           
3649            invalidate();
3650    
3651            // hover selection feedback curves (which are system curves)
3652            // never impact curve counts, need to refresh decorations, etc.
3653            if (isSystemCurve()) {
3654               this.isVisible = isVisible;
3655               return;
3656            }
3657            
3658            if (this.isVisible != isVisible) {
3659               Axis yaxis = (getYAxis() == Y_AXIS) ?
3660                             GChart.this.getYAxis() :
3661                             GChart.this.getY2Axis();
3662               boolean axisCreatedOrDestroyed;
3663               if (isVisible) {
3664                 axisCreatedOrDestroyed =
3665                    (yaxis.getNCurvesVisibleOnAxis() == 0);
3666                 getXAxis().incrementCurves();
3667                 yaxis.incrementCurves();
3668               }
3669               else {
3670                 getXAxis().decrementCurves();
3671                 yaxis.decrementCurves(); 
3672                 axisCreatedOrDestroyed =
3673                   (yaxis.getNCurvesVisibleOnAxis() == 0);
3674               }
3675    
3676               if ((null != getLegendLabel() && isLegendVisible()) ||
3677                   axisCreatedOrDestroyed)
3678                 chartDecorationsChanged = true;
3679    
3680               this.isVisible = isVisible;
3681            }
3682         }
3683         
3684      /** Sets the y-axis that this curve is plotted on.
3685       ** <p>
3686       ** @param axisId must be either GChart.Y_AXIS or
3687       **               GChart.Y2_AXIS
3688       **
3689       ** @see #getYAxis getYAxis
3690       ** @see GChart#Y_AXIS Y_AXIS
3691       ** @see GChart#Y2_AXIS Y2_AXIS
3692       ** 
3693       ** 
3694       **/
3695        public void setYAxis(YAxisId axisId) {
3696           invalidate();
3697           if (isSystemCurve()) {
3698              yAxisId = axisId;
3699           }
3700           else if (axisId != yAxisId) {
3701              if (axisId == Y2_AXIS) { // from Y to Y2
3702                     GChart.this.getYAxis().decrementCurves();
3703                     GChart.this.getY2Axis().incrementCurves();
3704              }
3705              else {   // from Y2 to Y
3706                     GChart.this.getY2Axis().decrementCurves();
3707                     GChart.this.getYAxis().incrementCurves();
3708              }
3709              yAxisId = axisId;
3710           }
3711        }
3712    
3713        // Is this specific curve actually clipped to the plot area? 
3714        private boolean getActuallyClippedToPlotArea() {
3715           boolean result = getClipToPlotArea();
3716           if (result) {
3717             // decorative, hover feedback curves are never clipped 
3718             int rpIndex = getRenderingPanelIndex(getIndexOf());
3719             if (PlotPanel.DECORATIVE_RENDERING_PANEL_INDEX == rpIndex ||
3720                 isHoverFeedbackRenderingPanel(rpIndex))
3721                result = false;
3722           }
3723           return result;
3724        }
3725        
3726        /*
3727         * Is this curve one of GChart's special, internally created, system
3728         * curves? These curves can't be directly accessed by users, and are
3729         * used by GChart to render special features of the chart, such as
3730         * the hover selection cursors, titles, footnotes, etc.
3731         * 
3732         */ 
3733        private boolean isSystemCurve() {
3734           // negative curve indexes are reserved for system curves
3735           boolean result = (indexOf != GChart.NAI) &&
3736                            externalCurveIndex(indexOf) < 0;
3737           return result;
3738        }
3739      // renders the specified point of this curve on the given panel
3740        void realizePoint(PlotPanel pp,
3741                          GraphicsRenderingPanel grp,
3742                          AnnotationRenderingPanel arp,
3743                          int iPoint) {
3744         Point p = points.get(iPoint);
3745         double x = p.getX();
3746         double y = p.getY();
3747         // skip points at undefined locations
3748         if ((x!=x) || (y!=y)) return; // x!=x is a faster isNaN
3749         double prevX = Double.NaN;
3750         double prevY = Double.NaN;
3751         if (iPoint > 0) {
3752            Point prevP = points.get(iPoint-1);
3753            prevX = prevP.getX();
3754            prevY = prevP.getY();
3755         }
3756         double nextX = Double.NaN;
3757         double nextY = Double.NaN;
3758         Point nextP = null;
3759         if (iPoint < getNPoints()-1) {
3760            nextP = points.get(iPoint+1);
3761            nextX = nextP.getX();
3762            nextY = nextP.getY();
3763         }
3764    
3765         // if point was not assigned to any band, it's not drawn
3766         // at all (undefined x or y, or off chart entirely)
3767         boolean drawMainSymbol = (p.getINextInBand() != iPoint);
3768    
3769         getSymbol().realizeSymbol(pp, grp, arp, p.getAnnotation(), onY2(),
3770                                   getActuallyClippedToPlotArea(),
3771                                   getClipToDecoratedChart(),
3772                                   drawMainSymbol,
3773                                   x, y, prevX, prevY, nextX, nextY);
3774    //    }
3775      }
3776      /**
3777       ** Represents a single point on one of the chart's
3778       ** curves. This includes the x, y values of the point in
3779       ** "model coordinates" (arbitrary, application-specific,
3780       ** units), as well as an optional annotation (text label)
3781       ** for the point.
3782       ** <p>
3783       ** To create points, use a curve's <tt>addPoint</tt> method.
3784       ** 
3785       ** @see Curve#addPoint addPoint
3786       **/ 
3787      public class Point {
3788         
3789         // x, y location (user coordinates) of point
3790         // (points are drawn using the containing curve's symbol)
3791         private double x; 
3792         private double y; 
3793         Annotation annotation = null;
3794         // Points to index of next point in a vertical or horizontal
3795         // band (used by the <tt>bandSeparatePoints</tt> method).
3796         private int iNextInBand = GChart.NAI;
3797         int getINextInBand() { return iNextInBand;}
3798         void setINextInBand(int iNext) {iNextInBand = iNext;}
3799         Point(double x, double y) {
3800           this.x = x;   
3801           this.y = y;
3802         }
3803    
3804         /**
3805          ** Returns true if annotation will be rendered in a bold,
3806          ** or false if in normal, weight font. 
3807          **
3808          ** @return if this annotation is in bold or not.
3809          **
3810          ** @see #setAnnotationFontWeight setAnnotationFontWeight
3811          **/ 
3812         public String getAnnotationFontWeight() {
3813            if (null == annotation) annotation = new Annotation();   
3814            return annotation.getFontWeight();
3815         }
3816    
3817         /**
3818          ** Returns the color of the font used to display the point's
3819          **   annotation text.
3820          **   
3821          ** @return CSS color string defining the annotation's color
3822          **
3823          ** @see #setAnnotationFontColor setAnnotationFontColor
3824          **/ 
3825         public String getAnnotationFontColor() {
3826            if (null == annotation) annotation = new Annotation();   
3827            return annotation.getFontColor();
3828         }
3829    
3830         
3831         /**
3832          ** Returns the CSS font-style in which the text of this
3833          **  annotation will be rendered.
3834          **
3835          ** @return the font-style used by this annotation (italic,
3836          **   normal, etc.)
3837          **
3838          ** @see #setAnnotationFontStyle setAnnotationFontStyle
3839          **/ 
3840         public String getAnnotationFontStyle() {
3841            if (null == annotation) annotation = new Annotation();   
3842            return annotation.getFontStyle();
3843         }
3844    
3845         /**
3846          ** Returns the CSS font size of this point's annotation
3847          ** (text label), in pixels.
3848          **
3849          ** @return the font size of this point's annotation.
3850          **
3851          ** @see #setAnnotationFontSize setAnnotationFontSize
3852          **/ 
3853         public int getAnnotationFontSize() {
3854            if (null == annotation) annotation = new Annotation();   
3855            return annotation.getFontSize();
3856         }
3857    
3858         
3859         
3860         /** Returns the previously specified location, relative
3861          ** to the symbol representing the point, of the
3862          ** annotation (text label) associated with this point.
3863          **
3864          ** @return relative location of the point's annotation
3865          ** 
3866          ** @see #setAnnotationLocation setAnnotationLocation
3867          ** 
3868          **/ 
3869         public AnnotationLocation getAnnotationLocation() {
3870            if (null == annotation) annotation = new Annotation();   
3871            AnnotationLocation result = annotation.getLocation();
3872            if (null == result) result =
3873              getParent().getSymbol().getSymbolType().defaultAnnotationLocation();
3874            return result;
3875         }
3876         /**
3877          ** Returns the text of this point's annotation.
3878          **
3879          ** @return the text of the annotation, or <tt>null</tt> if this
3880          **   point either lacks an annotation or uses a widget-based
3881          **   annotation.
3882          **
3883          ** @see #setAnnotationText setAnnotationText
3884          ** 
3885          **/ 
3886         public String getAnnotationText() {
3887            if (null == annotation) annotation = new Annotation();   
3888            return annotation.getText();
3889         }
3890    
3891         /**
3892          * Returns the widget reference that defines this point's
3893          * annotation as previously specified by
3894          * <tt>setAnnotationWidget</tt>. Returns <tt>null</tt> if
3895          * the annotation has not yet been specified, or if it was
3896          * defined via <tt>setAnnotationText</tt>.
3897          *
3898          * @return reference to the widget defining this point's
3899          * annotation, or <tt>null</tt> if none.
3900          *
3901          * @see #setAnnotationWidget setAnnotationWidget
3902          * @see #setAnnotationText setAnnotationText
3903          *
3904          */
3905    
3906         public Widget getAnnotationWidget() {
3907            if (null == annotation) annotation = new Annotation();   
3908            return annotation.getWidget();
3909         }
3910         
3911         /**
3912          ** Returns true is the point's annotation is visible, false
3913          ** otherwise
3914          **
3915          ** @return if the a annotation defined for this point will
3916          **    be visible or not after the next update.
3917          **
3918          ** @see #setAnnotationVisible setAnnotationVisible
3919          **/ 
3920         public boolean getAnnotationVisible() {
3921            if (null == annotation) annotation = new Annotation();   
3922           return annotation.getVisible();
3923         }
3924    
3925         /**
3926          ** Returns the distance, in pixels, that this annotation
3927          ** will be shifted along the x-axis from it's default
3928          ** location.  <p>
3929          **
3930          ** @return amount annotation will be shifted along the x-axis,
3931          **   in pixels.
3932          **   
3933          ** @see #setAnnotationXShift setAnnotationXShift
3934          **/ 
3935          public int getAnnotationXShift() {
3936            if (null == annotation) annotation = new Annotation();   
3937            return annotation.getXShift();
3938          }
3939         
3940         /**
3941          ** Returns the distance, in pixels, that this annotation
3942          ** will be shifted along the y-axis from it's default
3943          ** location.  <p>
3944          **
3945          ** @return amount annotation will be shifted along the y-axis,
3946          **   in pixels.
3947          **   
3948          ** @see #setAnnotationYShift setAnnotationYShift
3949          **/ 
3950          public int getAnnotationYShift() {
3951            if (null == annotation) annotation = new Annotation();   
3952            return annotation.getYShift();
3953          }
3954    
3955          /** Returns the <tt>Curve</tt> that this point was added to.
3956           **
3957           ** @return a reference to the <tt>Curve</tt> that contains
3958           ** this point (its "parent").
3959           **
3960           **/ 
3961          public Curve getParent() {return Curve.this;}
3962          
3963         /** Returns the x-coordinate of this point in "model units"
3964          ** (arbitrary, application-specific, units). 
3965          **
3966          ** @return the x-coordinate, in model units
3967          **
3968          ** @see #setX setX
3969          ** @see #setY setY
3970          ** @see #getY getY
3971          ** 
3972          **/ 
3973         public double getX() {
3974            return x;
3975         }
3976         /** Returns the y-coordinate of this point in "model units"
3977          ** (arbitrary, application-specific, units). 
3978          **
3979          ** @return the y-coordinate, in model units
3980          **
3981          ** @see #getX getX
3982          ** @see #setX setX
3983          ** @see #setY setY
3984          ** 
3985          **/ 
3986         public double getY() {
3987            return y;
3988         }
3989    
3990         /**
3991          ** Specifies the weight of the font that will be used
3992          ** to render the text of this point's annotation.
3993          ** <p>
3994          ** 
3995          ** @param cssWeight A standard CSS font-weight
3996          **    specification such as normal, bold, bolder, lighter,
3997          **    100, 200, ... 900, or inherit
3998          **
3999          ** @see #getAnnotationFontWeight getAnnotationFontWeight  
4000          ** @see #setAnnotationFontColor setAnnotationFontColor
4001          ** @see #setAnnotationFontStyle setAnnotationFontStyle
4002          ** @see #setAnnotationFontSize setAnnotationFontSize
4003          ** @see #setAnnotationXShift setAnnotationXShift
4004          ** @see #setAnnotationYShift setAnnotationYShift
4005          ** @see #setAnnotationText setAnnotationText
4006          ** @see #setAnnotationVisible setAnnotationVisible
4007          **/ 
4008         public void setAnnotationFontWeight(String cssWeight) {
4009           getParent().invalidate();
4010           if (null == annotation) annotation = new Annotation();   
4011           annotation.setFontWeight(cssWeight);
4012         }
4013         /**
4014          ** Specifies the color of the annotation's font.
4015          **
4016          ** 
4017          ** <p>
4018          ** For more information on standard CSS color
4019          ** specifications see the discussion in
4020          ** {@link Symbol#setBackgroundColor Symbol.setBackgroundColor}.
4021          ** <p>
4022          **        
4023          ** @param cssColor color of the font used to display this
4024          **    point's annotation message.
4025          **
4026          ** @see #getAnnotationFontColor getAnnotationFontColor  
4027          ** @see #setAnnotationFontWeight setAnnotationFontWeight
4028          ** @see #setAnnotationFontStyle setAnnotationFontStyle
4029          ** @see #setAnnotationFontSize setAnnotationFontSize
4030          ** @see #setAnnotationXShift setAnnotationXShift
4031          ** @see #setAnnotationYShift setAnnotationYShift
4032          ** @see #setAnnotationText setAnnotationText
4033          ** @see #setAnnotationVisible setAnnotationVisible
4034          **/ 
4035         public void setAnnotationFontColor(String cssColor) {
4036            getParent().invalidate();
4037            if (null == annotation) annotation = new Annotation();   
4038            annotation.setFontColor(cssColor);
4039         }
4040    
4041         
4042         /**
4043          ** Specifies the CSS font-style used by this point's annotation
4044          ** message.
4045          **
4046          ** @param cssStyle any valid CSS font-style, namely,
4047          **   normal, italic, oblique, or inherit.
4048          **
4049          ** @see #getAnnotationFontStyle getAnnotationFontStyle  
4050          ** @see #setAnnotationFontWeight setAnnotationFontWeight
4051          ** @see #setAnnotationFontColor setAnnotationFontColor
4052          ** @see #setAnnotationFontSize setAnnotationFontSize
4053          ** @see #setAnnotationXShift setAnnotationXShift
4054          ** @see #setAnnotationYShift setAnnotationYShift
4055          ** @see #setAnnotationText setAnnotationText
4056          ** @see #setAnnotationVisible setAnnotationVisible
4057          **/ 
4058         public void setAnnotationFontStyle(String cssStyle) {
4059            getParent().invalidate();
4060            if (null == annotation) annotation = new Annotation();   
4061            annotation.setFontStyle(cssStyle);
4062         }
4063         /**
4064          ** Specifies the CSS font size of this point's annotation, in
4065          ** pixels.
4066          **
4067          ** @param fontSize the font size of this point's annotation, in
4068          **   pixels.
4069          **
4070          ** @see #getAnnotationFontSize getAnnotationFontSize  
4071          ** @see #setAnnotationFontWeight setAnnotationFontWeight
4072          ** @see #setAnnotationFontColor setAnnotationFontColor
4073          ** @see #setAnnotationFontStyle setAnnotationFontStyle
4074          ** @see #setAnnotationXShift setAnnotationXShift
4075          ** @see #setAnnotationYShift setAnnotationYShift
4076          ** @see #setAnnotationText setAnnotationText
4077          ** @see #setAnnotationVisible setAnnotationVisible
4078          **/ 
4079         public void setAnnotationFontSize(int fontSize) {
4080            getParent().invalidate();
4081            if (null == annotation) annotation = new Annotation();   
4082            annotation.setFontSize(fontSize);
4083         }
4084         /**
4085          ** Specifies the location, relative to this point's symbol,
4086          ** of this point's annotation (text label).
4087          ** <p>
4088          ** 
4089          ** You can further adjust the position of a point's
4090          ** annotation by specifying non-zero positional shifts via
4091          ** the <tt>setAnnotationXShift</tt> and
4092          ** <tt>setAnnotationYShift</tt> methods.
4093          **
4094          ** 
4095          ** @param annotationLocation the relative location of
4096          ** the annotation
4097          **
4098          ** @see #getAnnotationLocation getAnnotationLocation
4099          ** @see #setAnnotationFontWeight setAnnotationFontWeight
4100          ** @see #setAnnotationFontColor setAnnotationFontColor
4101          ** @see #setAnnotationFontStyle setAnnotationFontStyle
4102          ** @see #setAnnotationFontSize setAnnotationFontSize
4103          ** @see #setAnnotationText setAnnotationText
4104          ** @see #setAnnotationXShift setAnnotationXShift
4105          ** @see #setAnnotationYShift setAnnotationYShift
4106          ** @see #setAnnotationVisible setAnnotationVisible
4107          ** 
4108          **/
4109         public void setAnnotationLocation(AnnotationLocation
4110                                           annotationLocation) {
4111           getParent().invalidate();
4112           if (null == annotation) annotation = new Annotation();   
4113           annotation.setLocation(annotationLocation);
4114         }
4115    
4116         /**
4117          ** Specifies the text of this point's annotation
4118          ** (label). 
4119          ** <p>
4120          ** 
4121          ** <p>By default text is plain text, though
4122          ** you can change the size, weight, style, and color of
4123          ** the text via the <tt>setAnnotationFont*</tt>
4124          ** family of methods.
4125          ** 
4126          ** <p>
4127          ** 
4128          ** <b>To use HTML, <i>your text must begin with</i>
4129          ** <tt>&lt;html&gt</tt></b> (otherwise, GChart will treat
4130          ** it as plain text). Note that the leading
4131          ** <tt>&lt;html&gt</tt> is stripped off by GChart before
4132          ** your HTML gets to the browser. Since it's just a flag
4133          ** for GChart, not a real HTML tag, you should <i>not</i>
4134          ** use a closing <tt>&lt;/html&gt</tt> at the end.
4135          **
4136          ** <p> <small> The idea for adding HTML support (only plain
4137          ** text was supported originally) came from <a
4138          ** href="http://groups.google.com/group/Google-Web-Toolkit/msg/cb89003dad2416fe">
4139          ** this GWT forum post by Malcolm Gorman</a>. The current
4140          ** HTML support (and, it's natural extension, Widget
4141          ** support) in tick labels and annotations, which seems so
4142          ** obvious in hindsight, might never have been added had it
4143          ** not been for this post. Thanks!</small>
4144          ** 
4145          ** <p>
4146          **
4147          ** <small><b>How to use the width and height upperbounds:</b>
4148          ** </small>
4149          ** 
4150          ** <p>
4151          ** 
4152          ** <blockquote><small>
4153          **
4154          ** 
4155          ** In most cases, you can safely ignore these two
4156          ** parameters, simply calling the {@link
4157          ** #setAnnotationText(String) 1-arg convenience method}
4158          ** and getting GChart to estimate them for you.
4159          ** <p>
4160          **
4161          ** The width and height upper-bounds define an invisible
4162          ** bounding box (a 1x1 GWT Grid, actualy) that is used to
4163          ** properly align and center your annotation.
4164          ** <p>
4165          ** 
4166          ** <p> Annotations can
4167          ** become misaligned if, say, due to the user zooming up
4168          ** their font size, an annotation's size exceeds these
4169          ** upperbounds. This misalignment problem can be fixed by
4170          ** specifying a larger width and/or height upperbound.
4171          ** But, larger upperbounds slow chart updates a bit.
4172          ** The defaults try to balance the performance and
4173          ** alignment tradeoff.
4174          ** <p>
4175          **
4176          ** There is one annoying but generally harmless side effect of
4177          ** using very large upperbounds: most browsers will extend their
4178          ** scroll regions to the right (and presumably below) the real
4179          ** page content so as to include the invisible bounding box. When
4180          ** this happens, it looks to the user as if there is a bunch of
4181          ** blank space on, say, the right edge of the page. In practice,
4182          ** I've always been able to prevent this problem simply by
4183          ** choosing at-least-somewhat-reasonably-tight upper bounds,
4184          ** though a little blank space may be unavoidable in some special
4185          ** cases.
4186          ** 
4187          ** </blockquote></small>
4188          ** 
4189          ** @param annotationText the text or (<tt>&lt;html&gt</tt>
4190          ** prefixed) HTML of this point's
4191          ** annotation, or <tt>null</tt> to remove all annotation.
4192          ** 
4193          ** @param widthUpperBound an upper bound on the width of
4194          ** the text or HTML, in pixels. Use <tt>GChart.NAI</tt> to
4195          ** get GChart to estimate this width using a heuristic
4196          ** that works fine most of the time.
4197          ** 
4198          ** @param heightUpperBound an upper bound on the height of
4199          ** the text or HTML, in pixels. Use <tt>GChart.NAI</tt> to
4200          ** get GChart to estimate this height using a heuristic
4201          ** that works fine most of the time.
4202          **
4203          ** @see #getAnnotationText getAnnotationText
4204          ** @see #setAnnotationText(String) setAnnotationText(String)
4205          ** @see #setAnnotationLocation setAnnotationLocation
4206          ** @see #setAnnotationFontWeight setAnnotationFontWeight
4207          ** @see #setAnnotationFontColor setAnnotationFontColor
4208          ** @see #setAnnotationFontStyle setAnnotationFontStyle
4209          ** @see #setAnnotationFontSize setAnnotationFontSize
4210          ** @see #setAnnotationWidget setAnnotationWidget
4211          ** @see #setAnnotationXShift setAnnotationXShift
4212          ** @see #setAnnotationYShift setAnnotationYShift
4213          ** @see #setAnnotationVisible setAnnotationVisible
4214          ** @see GChart.Axis#addTick(double,String,int,int) addTick
4215          ** 
4216          **/
4217         public void setAnnotationText(String annotationText,
4218                                       int widthUpperBound,
4219                                       int heightUpperBound) {
4220            getParent().invalidate();
4221            if (null == annotation) annotation = new Annotation();   
4222            annotation.setText(annotationText,
4223                               widthUpperBound,
4224                               heightUpperBound);
4225         }
4226         /**
4227          * Sets the text of an annotation.
4228          * <p>
4229          * This is a convenience method equivalent to
4230          * <tt>setAnnotationText(annotationText, GChart.NAI, GChart.NAI)</tt>. See
4231          * that method for further details.
4232          * <p>
4233          *
4234          ** @param annotationText the text or
4235          ** (<tt>&lt;html&gt</tt>-prefixed) HTML of this point's
4236          ** annotation, or <tt>null</tt> to remove all annotation.
4237          *
4238          * @see #setAnnotationText(String, int, int) 
4239          *       setAnnotationText(String,int,int)
4240          *       
4241          */ 
4242         public void setAnnotationText(String annotationText) {
4243              setAnnotationText(annotationText, GChart.NAI, GChart.NAI);
4244         }
4245    
4246         /**
4247          ** Specifies a widget defining this point's annotation
4248          ** <p>
4249          ** This method is similar to <tt>setAnnotationText</tt>
4250          ** except that it uses a widget, rather than a string
4251          ** to define this point's annotation.
4252          ** Although the string based method is faster
4253          ** on first chart rendering, and uses less memory, the
4254          ** widget-based method allows you to change the annotation
4255          ** independently of the chart--potentially bypassing (or
4256          ** at least speeding up) expensive chart updates later on.
4257          ** <p>
4258          **
4259          ** You might use a widget-based annotation to pop-up a
4260          ** message whenever the user clicks on a button underneath
4261          ** a particular data point on the chart, to include a small
4262          ** GWT <tt>Grid</tt> as a table embedded in the upper left
4263          ** hand corner of the chart, to trigger mouse-over events
4264          ** when the user hovers over a transparent image-based
4265          ** annotation centered on a particular point, etc.
4266          ** <p>
4267          ** 
4268          ** <i>Tip:</i>If you need to instrument a chart using
4269          ** widgets precisely positioned on the chart, but not
4270          ** associated with any visible curve, add a curve just to
4271          ** hold these annotations, with one point per annotation,
4272          ** and set that curve's symbol type
4273          ** to <tt>SymbolType.NONE</tt>.
4274          ** <p>
4275          ** 
4276          ** <b><i>Warning:</i></b> If you use the exact same widget
4277          ** reference to define two different annotations, GChart
4278          ** will render only one of them, and <i>there is no easy
4279          ** rule</i> that lets you reliably determine which one. So,
4280          ** don't do that.  Instead, if you want to use the same
4281          ** widget for two different annotations, use two identical
4282          ** but distinct copies of that widget. Similarly, if you
4283          ** want to move a single widget annotation from one point
4284          ** to another, be sure to "<tt>null</tt> out" the first
4285          ** point's annotation (e.g.  via
4286          ** <tt>setAnnotationWidget(null)</tt>) or the widget may
4287          ** not be rendered where you expect. You should also
4288          ** <tt>null</tt> out the annotation widget reference before
4289          ** moving the annotation widget to a position in the DOM
4290          ** completely outside of the GChart. A little extra
4291          ** bookkeeping on your part makes it possible to
4292          ** significantly simplify and streamline GChart's rendering
4293          ** algorithms. 
4294          **
4295          *  @param annotationWidget the GWT Widget that defines this
4296          *    point's annotation. 
4297          *
4298          *  @param widthUpperBound an upper bound on the width of
4299          *  the Widget, in pixels. If this and the next
4300          *  parameter are omitted, GChart will use
4301          *  <tt>DEFAULT_WIDGET_WIDTH_UPPERBOUND</tt>.
4302          *  
4303          *  @param heightUpperBound an upper bound on the height of
4304          *  the Widget, in pixels. If this and the previous
4305          *  parameter are omitted, GChart will use <tt>
4306          *  DEFAULT_WIDGET_HEIGHT_UPPERBOUND</tt>
4307          *
4308          * 
4309          * @see #getAnnotationWidget getAnnotationWidget
4310          * @see #setAnnotationText(String, int, int) 
4311          *       setAnnotationText(String,int,int)
4312          * @see #setAnnotationWidget(Widget)
4313          * setAnnotationWidget(Widget)
4314          * @see #DEFAULT_WIDGET_HEIGHT_UPPERBOUND DEFAULT_WIDGET_HEIGHT_UPPERBOUND
4315          * @see #DEFAULT_WIDGET_WIDTH_UPPERBOUND DEFAULT_WIDGET_WIDTH_UPPERBOUND
4316          * @see SymbolType#NONE  SymbolType.NONE
4317          * 
4318          **/ 
4319         public void setAnnotationWidget(Widget annotationWidget,
4320                                       int widthUpperBound,
4321                                       int heightUpperBound) {
4322            getParent().invalidate();
4323            if (null == annotation) annotation = new Annotation();   
4324         // accept "Not an Integer" (because setAnnotationText does)
4325           if (widthUpperBound == GChart.NAI)
4326              widthUpperBound = DEFAULT_WIDGET_WIDTH_UPPERBOUND;
4327           if (heightUpperBound == GChart.NAI)
4328              heightUpperBound = DEFAULT_WIDGET_HEIGHT_UPPERBOUND;
4329           annotation.setWidget(annotationWidget,
4330                                widthUpperBound,
4331                                heightUpperBound);
4332         }
4333    
4334         /**
4335          * Specifies a widget defining this point's annotation.
4336          * <p>
4337          * A convenience method equivalent to
4338          * <tt>setAnnotationWidget(annotationWidget,
4339          * DEFAULT_WIDGET_WIDTH_UPPERBOUND,
4340          * DEFAULT_WIDGET_HEIGHT_UPPERBOUND)</tt>
4341          *
4342          *  @param annotationWidget the GWT Widget that defines this
4343          *    point's annotation. 
4344          *
4345          * @see #setAnnotationWidget(Widget,int,int)
4346          * setAnnotationWidget(Widget,int,int)
4347          * @see #DEFAULT_WIDGET_HEIGHT_UPPERBOUND DEFAULT_WIDGET_HEIGHT_UPPERBOUND
4348          * @see #DEFAULT_WIDGET_WIDTH_UPPERBOUND DEFAULT_WIDGET_WIDTH_UPPERBOUND
4349          *
4350          */ 
4351         public void setAnnotationWidget(Widget annotationWidget) {
4352            setAnnotationWidget(annotationWidget, DEFAULT_WIDGET_WIDTH_UPPERBOUND,
4353                              DEFAULT_WIDGET_HEIGHT_UPPERBOUND);
4354         }
4355    
4356         
4357         /**
4358          ** Specifies if this point's annotation
4359          ** (label) is visible or not. 
4360          ** <p>
4361          ** 
4362          ** @param isVisible use true to make the annotation
4363          **   visible, or false to hide it.
4364          **
4365          ** @see #getAnnotationVisible getAnnotationVisible
4366          ** @see #setAnnotationLocation setAnnotationLocation
4367          ** @see #setAnnotationFontWeight setAnnotationFontWeight
4368          ** @see #setAnnotationFontColor setAnnotationFontColor
4369          ** @see #setAnnotationFontStyle setAnnotationFontStyle
4370          ** @see #setAnnotationFontSize setAnnotationFontSize
4371          ** @see #setAnnotationXShift setAnnotationXShift
4372          ** @see #setAnnotationYShift setAnnotationYShift
4373          ** @see #setAnnotationText setAnnotationText
4374          **/
4375          public void setAnnotationVisible(boolean isVisible) {
4376            getParent().invalidate();
4377            if (null == annotation) annotation = new Annotation();   
4378            annotation.setVisible(isVisible);
4379          }
4380    
4381          
4382    
4383         /**
4384          ** Specifies the number of pixels (along the x-axis) to
4385          ** move this point's annotation from its default,
4386          ** <tt>AnnotationLocation</tt>-defined, position.  Negative
4387          ** values move the annotation in the negative x direction.
4388          **
4389          ** <p> For example, with the default <tt>xShift</tt> of 0,
4390          ** annotations with an <tt>AnnotationLocation</tt> of
4391          ** <tt>EAST</tt> will have their left edges flush against
4392          ** the right edge of, say, a box symbol representing the
4393          ** annotated point.  You could use an <tt>xShift</tt>
4394          ** setting of 10 to move the annotation 10 pixels to the
4395          ** right and thus introduce some space between the
4396          ** annotation and the box.
4397          ** <p>
4398          **
4399          ** <i>Special convention for pie slices:</i>
4400          ** Points on curves whose symbols represent pie
4401          ** slices always have the positive x-axis associated with
4402          ** the shifts specified by this method aligned with the
4403          ** outward-pointing pie radius that bisects the pie slice. This
4404          ** convention makes it easy to move pie slice annotations
4405          ** radially outward (via <tt>xShift > 0</tt>) or
4406          ** radially inward (via <tt>xShift < 0</tt>). For those
4407          ** rare situations where you may need to move a pie
4408          ** annotation perpendicularly to this radius, use
4409          ** <tt>setAnnotationYShift</tt>.
4410          ** 
4411          ** @param xShift number of pixels to move annotation
4412          **   along the x axis from
4413          **   it's default, <tt>AnnotationLocation</tt>-defined,
4414          **   location. 
4415          **
4416          ** @see #setAnnotationYShift setAnnotationYShift
4417          ** @see #setAnnotationLocation setAnnotationLocation
4418          ** @see #setAnnotationFontWeight setAnnotationFontWeight
4419          ** @see #setAnnotationFontColor setAnnotationFontColor
4420          ** @see #setAnnotationFontStyle setAnnotationFontStyle
4421          ** @see #setAnnotationFontSize setAnnotationFontSize
4422          ** @see #setAnnotationText setAnnotationText
4423          ** @see #setAnnotationVisible setAnnotationVisible
4424          ** @see #getAnnotationXShift getAnnotationXShift 
4425          **/
4426          public void setAnnotationXShift(int xShift) {
4427            getParent().invalidate();
4428            if (null == annotation) annotation = new Annotation();   
4429            annotation.setXShift(xShift);
4430          }
4431         /**
4432           ** Specifies the number of pixels (along the y-axis) to
4433          ** move this point's annotation from its default,
4434          ** <tt>AnnotationLocation</tt>-defined, position.  Negative
4435          ** values move the annotation in the negative y direction.
4436          **
4437          ** <p> For example, with the default <tt>yShift</tt> of 0,
4438          ** annotations with an <tt>AnnotationLocation</tt> of
4439          ** <tt>SOUTH</tt> will have their top edges flush against
4440          ** the bottom edge of, say, a box symbol representing the
4441          ** annotated point.  You could use a <tt>yShift</tt>
4442          ** setting of -10 to move the annotation down 10 pixels and
4443          ** thus introduce some spacing between the annotation and
4444          ** the box.
4445          ** <p>
4446          **
4447          ** <i>Special convention for pie slices:</i> The positive
4448          ** y-axis for pie slices always points one 90 degree
4449          ** counter-clockwise rotation from the direction of the
4450          ** outward-pointing pie radius that bisects the pie slice.
4451          ** This convention means that <tt>yShift</tt> moves pie
4452          ** slice annotations along a line <i>perpendicular to</i>
4453          ** this bisecting pie radius. Use the companion method
4454          ** <tt>setAnnotationXShift</tt> for the more common
4455          ** operation of moving the annotation along this bisecting
4456          ** radius.
4457          ** 
4458          ** @param yShift number of pixels to move annotation along
4459          **   the y-axis from it's default,
4460          **   <tt>AnnotationLocation</tt>-defined, location. 
4461          **
4462          ** @see #setAnnotationXShift setAnnotationXShift
4463          ** @see #setAnnotationLocation setAnnotationLocation
4464          ** @see #setAnnotationFontWeight setAnnotationFontWeight
4465          ** @see #setAnnotationFontColor setAnnotationFontColor
4466          ** @see #setAnnotationFontStyle setAnnotationFontStyle
4467          ** @see #setAnnotationFontSize setAnnotationFontSize
4468          ** @see #setAnnotationText setAnnotationText
4469          ** @see #setAnnotationVisible setAnnotationVisible
4470          ** @see #getAnnotationXShift getAnnotationXShift 
4471          **/
4472          public void setAnnotationYShift(int yShift) {
4473            getParent().invalidate();
4474            if (null == annotation) annotation = new Annotation();   
4475            annotation.setYShift(yShift);
4476          }
4477    
4478         /**
4479          * Defines the x-coordinate of this point in "model units"
4480          * (arbitrary, application-specific, units mapped to the
4481          * horizontal dimension of the plot area).
4482          * <p>
4483          *
4484          * <tt>Double.NaN</tt>, <tt>Double.MAX_VALUE</tt>, and
4485          * <tt>-Double.MAX_VALUE</tt> have special meanings. See the
4486          * <tt>addPoint</tt> method for details.
4487          *
4488          *
4489          * @param x the x-coordinate of the point in model units.
4490          * 
4491          ** @see #getX getX
4492          ** @see #setY setY
4493          ** @see #getY getY
4494          ** @see #addPoint addPoint 
4495          */ 
4496         public void setX(double x) {
4497            getParent().invalidate();
4498            this.x = x;
4499         }
4500    
4501         /**
4502          * Defines the y-coordinate of this point in "model units"
4503          * (arbitrary, application-specific, units mapped to the
4504          * vertical dimension of the plot area).
4505          * <p>
4506          *
4507          * <tt>Double.NaN</tt>, <tt>Double.MAX_VALUE</tt>, and
4508          * <tt>-Double.MAX_VALUE</tt> have special meanings. See the
4509          * <tt>addPoint</tt> method for details.
4510          *
4511          * @param y the y-coordinate of the point in model units.
4512          * 
4513          ** @see #getX getX
4514          ** @see #setX setX
4515          ** @see #getY getY
4516          ** @see #addPoint addPoint 
4517          ** 
4518          */ 
4519         public void setY(double y) {
4520            getParent().invalidate();
4521            this.y = y;
4522         }
4523    
4524         Annotation getAnnotation() {
4525            if (annotation == null) annotation = new Annotation();
4526            return annotation;
4527         }
4528         /**
4529          * Retrieves the expanded hovertext associated with this
4530          * point.
4531          * <p>
4532          *
4533          * The expanded hovertext is obtained by replacing any
4534          * embedded parameters in the hovertext template with
4535          * their values as evaluated at this point. For example,
4536          * references to <tt>${x}</tt> and <tt>${y}</tt> in
4537          * the hovertext template are replaced with
4538          * appropriately formatted representations of this
4539          * point's x and y properties.
4540          * <p>
4541          *
4542          * By default, GChart will display this expanded hovertext
4543          * whenever the user "touches" a point with the
4544          * curve-specific, rectangular, mouse-centered, brush.
4545          * <p>
4546          *
4547          * <i>Tip:</i> To define your own custom parameter names
4548          * that can be embedded within hovertext templates and
4549          * will be interpreted/expanded relative to the touched point, use
4550          * the <tt>setHoverParameterInterpreter</tt> method.
4551          *
4552          * @return the expanded hover text associated with this point.
4553          *
4554          * @see Symbol#setHovertextTemplate setHovertextTemplate
4555          * @see #setHoverParameterInterpreter setHoverParameterInterpreter
4556          *
4557          */
4558         public String getHovertext() {
4559            String result = HovertextChunk.getHovertext(
4560               getParent().getSymbol().getHovertextChunks(), this);
4561            return result;
4562              }
4563         
4564      } // end of class GChart.Curve.Point
4565    
4566      /*
4567       * Declares that this curve's rendering panel (its DOM representation)
4568       * is inconsistent with current curve specifications.
4569       * 
4570       * <p>
4571       *
4572       * Sets the flag <tt>update</tt> uses to determine if a curve needs
4573       * to be re-rendered.
4574       *
4575       */
4576      void invalidate() {
4577         // The guard isn't just for speed; it keeps us out of trouble
4578         // when the system curves are being added/configured initially
4579         if (isValidated) { 
4580           isValidated = false;
4581         // for efficiency, all background curves use a single rendering
4582         // panel, so invalidating one background curve invalidates them all
4583           if (indexOf < N_PRE_SYSTEM_CURVES) {
4584              for (int i = 0; i < N_PRE_SYSTEM_CURVES; i++)
4585                 curves.get(i).isValidated = false;
4586           }
4587         }     
4588      }
4589    
4590      /*
4591       * Smallest rectangle containing curve's graphics (ignoring
4592       * annotations)
4593       * <p>
4594       * 
4595       * Each curve has it's own canvas to allow for fast, single
4596       * curve, updates (and we hope this rendering independence will
4597       * facilitate additional features in future releases). So, we
4598       * have to economize on canvas size.  Moreover, because we
4599       * allow rendering outside of the decorated chart region, we
4600       * can't just set the size of the canvas to the size of the
4601       * plot area or decorated chart (even if we could afford to do
4602       * that, memory-wise).
4603       * 
4604       */ 
4605      Rectangle getContainingRectangle(PlotPanel pp) {
4606         final Rectangle result = new Rectangle();
4607         if (getNPoints() == 0) {
4608            result.x = result.y = result.width = result.height = 0;
4609            return result;
4610         }
4611         
4612         double minX = Double.MAX_VALUE;
4613         double maxX = -Double.MAX_VALUE;
4614         double minY = Double.MAX_VALUE;
4615         double maxY = -Double.MAX_VALUE;
4616         boolean pointAtXAxisMin = false; // do keyword positioned points
4617         boolean pointAtXAxisMax = false; // exist on this curve?
4618         boolean pointAtYAxisMin = false;
4619         boolean pointAtYAxisMax = false;
4620         boolean isClippedToDecoratedChart = getClipToDecoratedChart();
4621         boolean isClippedToPlotArea = getActuallyClippedToPlotArea();
4622         // Find min, max for x,y and record each keyword position used
4623         int nPoints = getNPoints();
4624         for (int i = 0; i < nPoints; i++) {
4625            Point p = getPoint(i);
4626            double x = p.getX();
4627            double y = p.getY();
4628            if (Double.MAX_VALUE == x)
4629               pointAtXAxisMax = true; 
4630            else if (-Double.MAX_VALUE == x)
4631               pointAtXAxisMin = true;
4632            else {
4633              if (x < minX) minX = x;
4634              if (x > maxX) maxX = x;  
4635            }
4636            if (Double.MAX_VALUE == y)
4637               pointAtYAxisMax = true; 
4638            else if (-Double.MAX_VALUE == y)
4639               pointAtYAxisMin = true;
4640            else {
4641              if (y < minY) minY = y;
4642              if (y > maxY) maxY = y;  
4643            }
4644         }
4645    
4646         // apply "at min/max" keyword, clipping imposed limits
4647         if (pointAtXAxisMin) 
4648            minX = Math.min(minX, pp.getXMin());
4649         if (isClippedToPlotArea)
4650            minX = Math.max(minX, pp.getXMin());
4651         else if (isClippedToDecoratedChart)
4652            minX = Math.max(minX, getXAxis().pixelToModel(0));
4653         
4654         if (pointAtXAxisMax) 
4655           maxX = Math.max(maxX, pp.getXMax());
4656         if (isClippedToPlotArea)
4657           maxX = Math.min(maxX, pp.getXMax());
4658         else if (isClippedToDecoratedChart)
4659           maxX = Math.min(maxX, getXAxis().pixelToModel(
4660                       pp.getXChartSizeDecoratedQuickly()));
4661    
4662         boolean onY2 = onY2();
4663         if (onY2) {
4664           if (pointAtYAxisMin) 
4665             minY = Math.min(minY, pp.getY2Min());
4666           if (isClippedToPlotArea)
4667             minY = Math.max(minY, pp.getY2Min());
4668           else if (isClippedToDecoratedChart)
4669             minY = Math.max(minY, getY2Axis().pixelToModel(
4670                 pp.getYChartSizeDecoratedQuickly()));
4671           if (pointAtYAxisMax) 
4672             maxY = Math.max(maxY, pp.getY2Max());
4673           if (isClippedToPlotArea)
4674             maxY = Math.min(maxY, pp.getY2Max());
4675           else if (isClippedToDecoratedChart)
4676             maxY = Math.min(maxY, getY2Axis().pixelToModel(0));
4677         }
4678         else {
4679           if (pointAtYAxisMin) 
4680             minY = Math.min(minY, pp.getYMin());
4681           if (isClippedToPlotArea)
4682             minY = Math.max(minY, pp.getYMin());
4683           else if (isClippedToDecoratedChart)
4684             minY = Math.max(minY, GChart.this.getYAxis().pixelToModel(
4685                 pp.getYChartSizeDecoratedQuickly()));
4686           if (pointAtYAxisMax) 
4687             maxY = Math.max(maxY, pp.getYMax());
4688           if (isClippedToPlotArea)
4689             maxY = Math.min(maxY, pp.getYMax());
4690           else if (isClippedToDecoratedChart)
4691             maxY = Math.min(maxY, GChart.this.getYAxis().pixelToModel(0));
4692         }
4693    
4694    // finally, we need to convert to pixels while taking into account
4695    // the size of the rendered symbol itself (e.g. pies can stick
4696    // out from their x,y specified center point, etc.)     
4697         Symbol sym = getSymbol();
4698         SymbolType symType = sym.getSymbolType();
4699    // in obscure cases, canvas could clip without this extra wiggle room
4700         int extraSpace = sym.getFillThickness();
4701         extraSpace += Math.abs(sym.getBorderWidth());
4702         double left0 = symType.getEdgeLeft(pp, sym, minX, onY2);
4703         double left1 = symType.getEdgeLeft(pp, sym, maxX, onY2);
4704         double right0 = symType.getEdgeRight(pp, sym, minX, onY2);
4705         double right1 = symType.getEdgeRight(pp, sym, maxX, onY2);
4706         double bottom0  = symType.getEdgeBottom(pp, sym, minY, onY2);
4707         double bottom1  = symType.getEdgeBottom(pp, sym, maxY, onY2);
4708         double top0 = symType.getEdgeTop(pp, sym, minY, onY2);
4709         double top1 = symType.getEdgeTop(pp, sym, maxY, onY2);
4710    
4711    // baseline bars can flip order, so smallest x could be 'right', etc.
4712         double xPxMin = Math.min(Math.min(left0, left1),
4713                                  Math.min(right0, right1));
4714         double xPxMax = Math.max(Math.max(left0, left1),
4715                                  Math.max(right0, right1));
4716         double yPxMin = Math.min(Math.min(bottom0, bottom1),
4717                                  Math.min(top0, top1));
4718         double yPxMax = Math.max(Math.max(bottom0, bottom1),
4719                                  Math.max(top0, top1));
4720         result.x = xPxMin - extraSpace;
4721         result.y = yPxMin - extraSpace;
4722         result.width = xPxMax - xPxMin + 1 + 2*extraSpace;
4723         result.height = yPxMax - yPxMin + 1 + 2*extraSpace;       
4724    
4725         // result is (roughly) smallest rectangle that contains every
4726         // rendered symbol on this curve (ignoring annotations)
4727         
4728         return result;
4729    
4730      }
4731    
4732      // keeps track of if last rendering was canvas-based or not   
4733      private boolean wasCanvasRendered = false;
4734      void setWasCanvasRendered(boolean wasCanvasRendered) {
4735         this.wasCanvasRendered = wasCanvasRendered;
4736      }
4737      // is curve currently canvas rendered and up-to-date
4738      boolean isCanvasRendered() {
4739         return isValidated && wasCanvasRendered;
4740      }
4741      
4742    } // end of class GChart.Curve       
4743    
4744    
4745         // Allows hovertext templates to be parsed into "chunks"
4746         // so that they can be expanded into hovertext faster.
4747         static class HovertextChunk {
4748            final static int HOVERTEXT_PARAM_NONE = 0; // plain old text
4749            final static int HOVERTEXT_PARAM_X = 1;  // ${x}
4750            final static int HOVERTEXT_PARAM_Y = 2;  // ${y}
4751            final static int HOVERTEXT_PARAM_PIESLICESIZE = 3; // ${pieSlicePercent}
4752            final static int HOVERTEXT_PARAM_USERDEFINED = 4; // ${mySpecialParameter}
4753            int paramId;      // id of substitution parameter
4754            String paramName;  // name of substitution parameter
4755            String chunkText; // plain text that follows this parameter
4756            HovertextChunk(int id, String name, String text) {
4757               paramId = id;
4758               paramName = name;
4759               chunkText = text;
4760            }
4761            // returns array of "chunks" corresponding to the given
4762            // hovertext template
4763            static HovertextChunk[] parseHovertextTemplate(
4764               String htTemplate) {
4765               if (htTemplate.equals("")) return new HovertextChunk[0];
4766               // takes "x=${x}; y=${y}" into {"x=", "x}; y=", "y}"}
4767               // Thus, except for the first, chunks contain a
4768               // keyword like part, followed by a string literal.
4769               String[] sChunk = htTemplate.split("\\$\\{");
4770               HovertextChunk[] result = new HovertextChunk[sChunk.length];
4771    
4772               for (int i = 0; i < sChunk.length; i++) {
4773                  String sC = sChunk[i];
4774                  if (0 == i) 
4775                  // leading (non-parametric) plain text chunk 
4776                     result[i] = new HovertextChunk(HOVERTEXT_PARAM_NONE,
4777                                                    null, sC);                 
4778                  else if (sC.startsWith("x}")) 
4779                     result[i] = new HovertextChunk(
4780                                       HOVERTEXT_PARAM_X, "x",
4781                                       sC.substring("x}".length()));
4782                  else if (sC.startsWith("y}")) 
4783                     result[i] = new HovertextChunk(
4784                                       HOVERTEXT_PARAM_Y, "y",
4785                                       sC.substring("y}".length()));
4786                  else if (sC.startsWith("pieSliceSize}")) 
4787                     result[i] = new HovertextChunk(
4788                                      HOVERTEXT_PARAM_PIESLICESIZE,
4789                                      "pieSliceSize",
4790                                      sC.substring("pieSliceSize}".length()));
4791                  else if (sC.matches("[a-zA-Z][a-zA-Z0-9_]*\\}.*")) {
4792                     // fits pattern for a user defined parameter
4793                     int closeCurlyIndex = sC.indexOf("}"); 
4794                     result[i] = new HovertextChunk(
4795                                       HOVERTEXT_PARAM_USERDEFINED,
4796                                       sC.substring(0, closeCurlyIndex),             
4797                                       sC.substring(closeCurlyIndex+1));
4798                  }
4799                  else {
4800                  // leading "${" without "paramName}". Likely a
4801                  // typo, but output verbatim to give them a clue:
4802                     result[i] = new HovertextChunk(HOVERTEXT_PARAM_NONE,
4803                                                    null, "${" + sC);
4804                  }
4805                     
4806               }
4807               return result;
4808            }
4809    
4810         /* hovertext associated with parsed "chunks" for a given point */   
4811         static String getHovertext(HovertextChunk[] htc,
4812                                    Curve.Point p) {
4813           String result = "";
4814           String xS = null;
4815           String yS = null;
4816           String pieSlicePercentS = null;
4817           HoverParameterInterpreter hpi =
4818             p.getParent().getParent().getHoverParameterInterpreter(); 
4819           for (int i = 0; i < htc.length; i++) {
4820              switch (htc[i].paramId) {
4821                 case HovertextChunk.HOVERTEXT_PARAM_NONE:
4822                    break;
4823                 case HovertextChunk.HOVERTEXT_PARAM_X:
4824                    if (null == xS) {
4825                       String hoverParam = (null == hpi) ? null :
4826                                  hpi.getHoverParameter(htc[i].paramName, p);
4827                       if (null != hoverParam)
4828                          xS = hoverParam;
4829                       else {
4830                          Axis axis =
4831                            p.getParent().getParent().getXAxis();
4832                          xS = axis.formatAsTickLabel(p.getX());
4833                       }
4834                    }
4835                    result += xS;
4836                    break;
4837                 case HovertextChunk.HOVERTEXT_PARAM_Y:
4838                    if (null == yS) {
4839                       String hoverParam = (null == hpi) ? null :
4840                                  hpi.getHoverParameter(htc[i].paramName, p);
4841                       if (null != hoverParam)
4842                          yS = hoverParam;
4843                       else {
4844                          Axis axis = p.getParent().onY2() ?
4845                              p.getParent().getParent().getY2Axis() :
4846                              p.getParent().getParent().getYAxis();
4847                          yS = axis.formatAsTickLabel(p.getY());
4848                       }
4849                    }
4850                    result+=yS;
4851                    break;
4852                      
4853                 case HovertextChunk.HOVERTEXT_PARAM_PIESLICESIZE:
4854                    if (null == pieSlicePercentS) {
4855                       String hoverParam = (null == hpi) ? null :
4856                                  hpi.getHoverParameter(htc[i].paramName, p);
4857                       if (null != hoverParam)
4858                          pieSlicePercentS = hoverParam;
4859                       else {
4860                          double pieSliceSize = 
4861                             p.getParent().getSymbol().getPieSliceSize();
4862                          Axis axis = p.getParent().onY2() ?
4863                                  p.getParent().getParent().getY2Axis() :
4864                                  p.getParent().getParent().getYAxis();
4865                          pieSlicePercentS =
4866                            axis.formatAsTickLabel(100*pieSliceSize) + "%";
4867                       }
4868                    }
4869                    result+=pieSlicePercentS;
4870                    break;
4871    
4872                 case HovertextChunk.HOVERTEXT_PARAM_USERDEFINED:
4873                    
4874                    String hoverParam = (null == hpi) ? null :
4875                               hpi.getHoverParameter(htc[i].paramName, p);
4876                    if (null == hoverParam)
4877                    // null means "unrecognized parameter" - so
4878                    // regenerate the original, unparsed, param spec
4879                    // to clue them in that it was not processed.
4880                       result += "${" + htc[i].paramName + "}"; 
4881                    else
4882                       result += hoverParam;
4883                       
4884                    break;
4885                 default:
4886                    throw new IllegalStateException(
4887                      "An illegal HOVERTEXT_PARAM_* id: " + htc[i].paramId + 
4888                      " was encountered. A GChart bug is likely to blame.");   
4889              }
4890              result+=htc[i].chunkText;
4891           }
4892           return result;
4893         }
4894       }
4895    
4896      /**
4897        ** Defines a chart curve symbol. Each point on a curve
4898        ** is represented on the chart by an appropriate
4899        ** rendering of the curve's symbol.
4900        **
4901        ** @see Curve#getSymbol Curve.getSymbol
4902        ** @see SymbolType SymbolType
4903        ** 
4904        **/
4905    
4906      public class Symbol {
4907    
4908         private Annotation annotation = null;
4909         private String backgroundColor = DEFAULT_SYMBOL_BACKGROUND_COLOR;
4910    // same as backgroundColor, but with extended RGBA collapsed to plain RGA
4911         private String backgroundColorCSS = DEFAULT_SYMBOL_BACKGROUND_COLOR;
4912         private double baseline = Double.NaN;
4913         private String borderColor = "black";
4914         private String borderColorCSS = "black";
4915         private String  borderStyle = DEFAULT_SYMBOL_BORDER_STYLE;
4916         private int  borderWidth = DEFAULT_SYMBOL_BORDER_WIDTH;
4917         private int brushHeight = DEFAULT_BRUSH_HEIGHT;
4918         private AnnotationLocation brushLocation = AnnotationLocation.CENTER;
4919         private int brushWidth = DEFAULT_BRUSH_WIDTH;
4920         private boolean fillHasHovertext = true;
4921         private double fillSpacing = Double.NaN;
4922         private int fillThickness = GChart.NAI;
4923         private int height = DEFAULT_SYMBOL_HEIGHT;
4924         private String hovertextTemplate=null;
4925         // holds specification for the hover annotation. Actual
4926         // hover annotation is generated on the fly when they hover
4927         private Annotation hoverAnnotation = null;
4928         private boolean hoverAnnotationEnabled = true;
4929         // allows hover annotation to use a different symbol type
4930         // than the symbol being hovered over. Main use expected to
4931         // be to place hover feedback at a fixed location on the chart (via
4932         // ANCHOR_* family of symbol types), for example, a status
4933         // bar message that changes depending on what the mouse
4934         // is touching.
4935         private SymbolType hoverAnnotationSymbolType = null;
4936         // encloses each symbol in a 1 px gray selection rectangle: 
4937         private String hoverSelectionBackgroundColor = "transparent";
4938         private String hoverSelectionBorderColor = "gray";
4939         private String hoverSelectionBorderStyle = "solid";
4940         private int hoverSelectionBorderWidth = -1;
4941         private boolean hoverSelectionEnabled = true;
4942         private double hoverSelectionFillSpacing = Double.NaN;
4943         private int hoverSelectionFillThickness = GChart.NAI;
4944         private int hoverSelectionHeight = GChart.NAI;
4945         private String hoverSelectionImageURL = null;
4946         private int hoverSelectionWidth = GChart.NAI;
4947         private SymbolType hoverSelectionSymbolType = null;
4948    
4949         private HovertextChunk[] hovertextChunks = null;
4950         private String imageURL = null;
4951    // XXX: Symbols are used independently of Curves by the
4952    // realizeTick method. But it's probably better to render ticks
4953    // via specialized system curves. If/when that's implemented,
4954    // Symbol should become an inner class of Curve, and this
4955    // explicit parent pointer will no longer be required.  
4956         private Curve parent = null;
4957    // when specified, model width/height are in user-defined units.
4958         private double modelHeight = Double.NaN;
4959         private double modelWidth = Double.NaN;
4960    // NaN means "begin this slice where last slice left off, or at 
4961    // initialPieSliceOrientation if it is the first slice to be rendered"
4962         private double pieSliceOrientation = Double.NaN;
4963         private double defaultPieSliceOrientation = 0.0;
4964    // slices, by default, fill the entire pie (useful for drawing disks)
4965         private double pieSliceSize = 1;
4966    
4967         private SymbolType symbolType = DEFAULT_SYMBOL_TYPE;
4968         
4969         private int width = DEFAULT_SYMBOL_WIDTH;
4970         double xScaleFactor = 1.0;
4971         double yScaleFactor = 1.0;
4972         
4973         Symbol(Curve parent) {super(); this.parent = parent;}
4974    
4975    
4976         
4977         /** Returns the CSS background color of all the rectangular
4978          ** elements used in rendering the symbol. 
4979          **
4980          ** @return the CSS background color used to fill in the
4981          **  central (non-border) part of each rectangular element
4982          **  used to render a curve's symbol.
4983          **
4984          ** @see #setBackgroundColor(String) setBackgroundColor 
4985          **/ 
4986         public String getBackgroundColor() {
4987            return backgroundColor;
4988         }
4989         String getBackgroundColorCSS() {
4990            return backgroundColorCSS;
4991         }
4992         /** Returns the baseline value for this symbol,
4993          ** previously specified via <tt>setBaseline</tt>
4994          **
4995          **
4996          ** @return the previously specified baseline value for
4997          **   this symbol.
4998          **
4999          ** @see #setBaseline setBaseline 
5000          **/ 
5001         public double getBaseline() {
5002            return baseline;
5003         }
5004        /** Returns the CSS border color of all the rectangular
5005         ** elements used in rendering the symbol. 
5006         ** 
5007         ** <p>
5008         ** @return the color of the border of the rectangular elements
5009         **   used to render the symbol, in standard CSS format
5010         ** 
5011         ** @see #setBorderColor setBorderColor
5012         ** 
5013         **/ 
5014         public String getBorderColor() { 
5015           return borderColor;
5016         }
5017         String getBorderColorCSS() {
5018            return borderColorCSS;
5019         }
5020         /**
5021         ** Returns the border style of all of the rectangular
5022         ** elements from which this symbol is built.
5023         ** <p>
5024         ** @return the CSS borderStyle of this symbol's elements
5025         **         (dotted, dashed, solid, etc. )
5026         **  
5027         ** @see #setBorderStyle setBorderStyle
5028         **/
5029           public String getBorderStyle() {
5030              return borderStyle;
5031           }
5032    
5033         /**
5034         ** Returns the width of the border around each
5035         ** rectangular element used to render this symbol,
5036         ** in pixels.
5037         ** 
5038         ** <p> 
5039         ** @return the previously set border width (in pixels).
5040         **
5041         ** @see #setBorderWidth setBorderWidth
5042          */
5043           public int getBorderWidth() {
5044              return borderWidth;
5045           }
5046            /**
5047               *
5048               * Returns the height of the rectangular "brush" that defines
5049               * how close the mouse cursor must be to a rendered symbol for
5050               * the symbol to be considered to have been "touched" (which
5051               * causes the point's hover feedback to pop up).
5052               *
5053               * @return the height of the "brush", in pixels, associated
5054               * with this symbol/curve.
5055               *
5056               * @see #setBrushHeight setBrushHeight
5057               *
5058               */
5059              public int getBrushHeight() {
5060                      return brushHeight;
5061              }
5062    
5063         /**
5064          * 
5065          * Returns the location of the rectangular brush relative to
5066          * the current x,y coordinates of the mouse cursor.
5067          * <p>
5068          *
5069          * @return the location of the rectangular brush relative to
5070          * the x,y coordinates of the mouse cursor.
5071          * 
5072          * @see #setBrushLocation setBrushLocation
5073          */
5074              public AnnotationLocation getBrushLocation() {
5075                 return brushLocation;
5076              }
5077    
5078    
5079            /**
5080               *
5081               * Returns the width of the rectangular "brush" that defines
5082               * how close the mouse cursor must be to a rendered symbol for
5083               * the symbol to be considered to have been "touched" (which
5084               * causes the point's hover feedback to pop up).  <p>
5085               *
5086               * @return the width of the "brush", in pixels, associated
5087               *   with this symbol/curve.
5088               *
5089               * @see #setBrushWidth setBrushWidth
5090               * 
5091               */
5092              public int getBrushWidth() {
5093                      return brushWidth;
5094              }
5095    
5096         /**
5097          ** @deprecated
5098          **
5099          ** Returns the value previously set by setFillHasHovertext.
5100          ** 
5101          ** @see #setFillHasHovertext setFillHasHovertext
5102          **
5103          **/
5104           public boolean getFillHasHovertext() {
5105             return fillHasHovertext;
5106           }
5107           
5108           
5109         /**
5110         ** Returns the spacing between successive rectangular
5111         ** elements used to emulate any required non-rectangular
5112         ** features of the symbol.  <p>
5113         **
5114         ** 
5115         ** 
5116         ** @return the previously set (or the default, if the
5117         ** fillSpacing has been set to <tt>Double.NaN</tt>) fill spacing
5118         ** (in pixels).
5119         **
5120         ** @see #setFillSpacing setFillSpacing
5121         ** @see #setFillThickness setFillThickness
5122         ** 
5123          */
5124           public double getFillSpacing() {
5125             if ((fillSpacing!=fillSpacing)) // x!=x is a faster isNaN
5126               return symbolType.defaultFillSpacing();
5127             else
5128               return fillSpacing;
5129           }
5130    
5131         /**
5132         ** Returns the "thickness" of rectangular elements used to
5133         ** emulate any required non-rectangular features of the symbol.
5134         ** <p>
5135         ** 
5136         ** 
5137         ** @return the previously set (or the default, if the
5138         ** fillThickness has been set to <tt>GChart.NAI</tt>) fill
5139         ** thickness (in pixels).
5140         **
5141         ** @see #setFillThickness setFillThickness
5142         ** @see #setFillSpacing setFillSpacing
5143          */
5144           public int getFillThickness() {
5145             if (fillThickness==GChart.NAI)
5146               return symbolType.defaultFillThickness();
5147             else
5148               return fillThickness;
5149           }
5150    
5151           /* Retrieves the annotation that defines the properties of
5152            * the internally generated annotations used to display
5153            * hover feedback. */
5154           Annotation getHoverAnnotation() {
5155             if (hoverAnnotation == null) hoverAnnotation = new Annotation();
5156             return hoverAnnotation;
5157           }
5158         /**
5159          * Retrieves a boolean that indicates if point-specific
5160          * annotations popup whenever you hover over a point on the
5161          * curve associated with this symbol.<p>
5162          * 
5163          * @return true if hover-induced annotations popup, false otherwise.
5164          *
5165          * @see #setHoverAnnotationEnabled setHoverAnnotationEnabled
5166          * 
5167          */
5168         public boolean getHoverAnnotationEnabled() {
5169           return hoverAnnotationEnabled;
5170         }
5171         /**
5172          ** Retrieves the weight of the font that will be used
5173          ** with this symbol's hover annotations.
5174          ** <p>
5175          ** 
5176          ** @return the standard CSS font-weight
5177          **    specification such as normal, bold, bolder, lighter,
5178          **    100, 200, ... 900, or inherit used by hover
5179          **    annotations
5180          **
5181          ** @see #setHoverFontWeight setHoverFontWeight  
5182          **
5183          ** 
5184          **/ 
5185        public String getHoverFontWeight() {
5186          if (hoverAnnotation == null) hoverAnnotation = new Annotation();
5187          String result = hoverAnnotation.getFontWeight();
5188          return result;
5189        }
5190         /**
5191          ** Retrieves the font color of this symbol's hover
5192          ** annotations.
5193          ** 
5194          ** @return color of the font used to display this
5195          **    symbol's hover annotations
5196          **
5197          ** @see #setHoverFontColor setHoverFontColor  
5198          **/ 
5199         public String getHoverFontColor() {
5200           if (hoverAnnotation == null) hoverAnnotation = new Annotation();
5201           String result = hoverAnnotation.getFontColor();
5202           return result;
5203         }
5204    
5205         
5206         /**
5207          ** Retrieves the CSS font-style used with this symbol's
5208          ** hover annotations.
5209          **
5210          ** @return the CSS font-style, namely,
5211          **   normal, italic, oblique, or inherit of text displayed
5212          **   in the hover annotations associated with this symbol
5213          **
5214          ** @see #setHoverFontStyle setHoverFontStyle  
5215          **/ 
5216         public String getHoverFontStyle() {
5217           if (hoverAnnotation == null) hoverAnnotation = new Annotation();
5218           String result = hoverAnnotation.getFontStyle();
5219           return result;
5220         }
5221         /**
5222          ** Retrieves the CSS font size used with this symbol's hover
5223          ** annotations, in pixels.
5224          **
5225          ** @return the font size used in the text displayed
5226          ** in the hover annotations associated with this symbol.
5227          **
5228          ** @see #setHoverFontSize setHoverFontSize
5229          ** 
5230          **/ 
5231         public int getHoverFontSize() {
5232           if (hoverAnnotation == null) hoverAnnotation = new Annotation();
5233           int result = hoverAnnotation.getFontSize();
5234           return result;
5235         }
5236    
5237        /**
5238         * Retrieves point-relative location of this symbol's hover
5239         * annotations.  <p>
5240         * 
5241         * @return the relative location of the hover annotations for
5242         * all points on the curve associated with this symbol.
5243         *
5244         * @see #setHoverLocation setHoverLocation
5245         * @see #DEFAULT_HOVER_LOCATION DEFAULT_HOVER_LOCATION
5246         *
5247         */
5248        public AnnotationLocation getHoverLocation() {
5249           if (hoverAnnotation == null) hoverAnnotation = new Annotation();
5250           AnnotationLocation result = hoverAnnotation.getLocation();
5251           if (null == result) result = getSymbolType().defaultHoverLocation(); 
5252           return result;
5253        }
5254        /**
5255         * Retrieves the symbol type that will determine how the
5256         * hover annotations for this symbol gets positioned.
5257         * <p>
5258         *
5259         * @return <tt>SymbolType</tt> used to position hover
5260         * annotations, or <tt>null</tt> if the symbol type of the
5261         * hovered over point is being used.
5262         *
5263         * @see #setHoverAnnotationSymbolType setHoverAnnotationSymbolType
5264         *
5265         */
5266        public SymbolType getHoverAnnotationSymbolType() {
5267           return hoverAnnotationSymbolType;   
5268        }
5269         /**
5270          * Retrieves the background color used to indicate that the mouse is
5271          * "touching" (hovering over) a point.
5272          * 
5273          * @return a CSS color specification string that represents
5274          * the background color used to indicate "hover-selection".
5275          *
5276          * @see #setHoverSelectionBackgroundColor
5277          * setHoverSelectionBackgroundColor