KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > prefuse > action > layout > StackedAreaChart


1 package prefuse.action.layout;
2
3 import java.awt.geom.Rectangle2D JavaDoc;
4 import java.util.Arrays JavaDoc;
5 import java.util.Iterator JavaDoc;
6
7 import prefuse.Constants;
8 import prefuse.data.Table;
9 import prefuse.data.query.NumberRangeModel;
10 import prefuse.util.ArrayLib;
11 import prefuse.util.MathLib;
12 import prefuse.util.PrefuseLib;
13 import prefuse.util.ui.ValuedRangeModel;
14 import prefuse.visual.VisualItem;
15
16 /**
17  * Layout Action that computes a stacked area chart, in which a series of
18  * data values are consecutively stacked on top of each other.
19  *
20  * @author <a HREF="http://jheer.org">jeffrey heer</a>
21  */

22 public class StackedAreaChart extends Layout {
23
24     private String JavaDoc m_field;
25     private String JavaDoc m_start;
26     private String JavaDoc m_end;
27     
28     private String JavaDoc[] columns;
29     private double[] baseline;
30     private double[] peaks;
31     private float[] poly;
32     private double m_padding = 0.05;
33     private float m_threshold;
34     private Rectangle2D JavaDoc bounds;
35     
36     private int m_orientation = Constants.ORIENT_BOTTOM_TOP;
37     private boolean m_horiz = false;
38     private boolean m_top = false;
39     
40     private boolean m_norm = false;
41     private NumberRangeModel m_model;
42     
43     /**
44      * Create a new StackedAreaChart.
45      * @param group the data group to layout
46      * @param field the data field in which to store computed polygons
47      * @param columns the various data fields, in sorted order, that
48      * should be referenced for each consecutive point of a stack layer
49      */

50     public StackedAreaChart(String JavaDoc group, String JavaDoc field, String JavaDoc[] columns) {
51         this(group, field, columns, 1.0);
52     }
53     
54     /**
55      * Create a new StackedAreaChart.
56      * @param group the data group to layout
57      * @param field the data field in which to store computed polygons
58      * @param columns the various data fields, in sorted order, that
59      * should be referenced for each consecutive point of a stack layer
60      * @param threshold height threshold under which stacks should not
61      * be made visible.
62      */

63     public StackedAreaChart(String JavaDoc group, String JavaDoc field, String JavaDoc[] columns,
64                             double threshold)
65     {
66         super(group);
67         this.columns = columns;
68         baseline = new double[columns.length];
69         peaks = new double[columns.length];
70         poly = new float[4*columns.length];
71         
72         m_field = field;
73         m_start = PrefuseLib.getStartField(field);
74         m_end = PrefuseLib.getEndField(field);
75         setThreshold(threshold);
76         
77         m_model = new NumberRangeModel(0,1,0,1);
78     }
79     
80     // ------------------------------------------------------------------------
81

82     /**
83      * Set the data columns used to compute the stacked layout
84      * @param cols the various data fields, in sorted order, that
85      * should be referenced for each consecutive point of a stack layer
86      */

87     public void setColumns(String JavaDoc[] cols) {
88         columns = cols;
89     }
90     
91     /**
92      * Sets if the stacks are normalized, such that each
93      * column is independently scaled.
94      * @param b true to normalize, false otherwise
95      */

96     public void setNormalized(boolean b) {
97         m_norm = b;
98     }
99     
100     /**
101      * Indicates if the stacks are normalized, such that each
102      * column is independently scaled.
103      * @return true if normalized, false otherwise
104      */

105     public boolean isNormalized() {
106         return m_norm;
107     }
108     
109     /**
110      * Gets the percentage of the layout bounds that should be reserved for
111      * empty space at the top of the stack.
112      * @return the padding percentage
113      */

114     public double getPaddingPercentage() {
115         return m_padding;
116     }
117     
118     /**
119      * Sets the percentage of the layout bounds that should be reserved for
120      * empty space at the top of the stack.
121      * @param p the padding percentage to use
122      */

123     public void setPaddingPercentage(double p) {
124         if ( p < 0 || p > 1 )
125             throw new IllegalArgumentException JavaDoc(
126                     "Illegal padding percentage: " + p);
127         m_padding = p;
128     }
129     
130     /**
131      * Get the minimum height threshold under which stacks should not be
132      * made visible.
133      * @return the minimum height threshold for visibility
134      */

135     public double getThreshold() {
136         return m_threshold;
137     }
138     
139     /**
140      * Set the minimum height threshold under which stacks should not be
141      * made visible.
142      * @param threshold the minimum height threshold for visibility to use
143      */

144     public void setThreshold(double threshold) {
145         m_threshold = (float)threshold;
146     }
147     
148     /**
149      * Get the range model describing the range occupied by the value
150      * stack.
151      * @return the stack range model
152      */

153     public ValuedRangeModel getRangeModel() {
154         return m_model;
155     }
156     
157     /**
158      * Returns the orientation of this layout. One of
159      * {@link Constants#ORIENT_BOTTOM_TOP} (to grow bottom-up),
160      * {@link Constants#ORIENT_TOP_BOTTOM} (to grow top-down),
161      * {@link Constants#ORIENT_LEFT_RIGHT} (to grow left-right), or
162      * {@link Constants#ORIENT_RIGHT_LEFT} (to grow right-left).
163      * @return the orientation of this layout
164      */

165     public int getOrientation() {
166         return m_orientation;
167     }
168     
169     /**
170      * Sets the orientation of this layout. Must be one of
171      * {@link Constants#ORIENT_BOTTOM_TOP} (to grow bottom-up),
172      * {@link Constants#ORIENT_TOP_BOTTOM} (to grow top-down),
173      * {@link Constants#ORIENT_LEFT_RIGHT} (to grow left-right), or
174      * {@link Constants#ORIENT_RIGHT_LEFT} (to grow right-left).
175      * @param orient the desired orientation of this layout
176      * @throws IllegalArgumentException if the orientation value
177      * is not a valid value
178      */

179     public void setOrientation(int orient) {
180         if ( orient != Constants.ORIENT_TOP_BOTTOM &&
181              orient != Constants.ORIENT_BOTTOM_TOP &&
182              orient != Constants.ORIENT_LEFT_RIGHT &&
183              orient != Constants.ORIENT_RIGHT_LEFT) {
184             throw new IllegalArgumentException JavaDoc(
185                     "Invalid orientation value: "+orient);
186         }
187         m_orientation = orient;
188         m_horiz = (m_orientation == Constants.ORIENT_LEFT_RIGHT ||
189                    m_orientation == Constants.ORIENT_RIGHT_LEFT);
190         m_top = (m_orientation == Constants.ORIENT_TOP_BOTTOM ||
191                    m_orientation == Constants.ORIENT_LEFT_RIGHT);
192     }
193     
194 // TODO: support externally driven range specification (i.e. stack zooming)
195
// public void setRangeModel(NumberRangeModel model) {
196
// m_model = model;
197
// }
198

199     // ------------------------------------------------------------------------
200

201     /**
202      * @see prefuse.action.Action#run(double)
203      */

204     public void run(double frac) {
205         bounds = getLayoutBounds();
206         Arrays.fill(baseline, 0);
207         
208         // get the orientation specifics sorted out
209
float min = (float)(m_horiz?bounds.getMaxY() :bounds.getMinX());
210         float hgt = (float)(m_horiz?bounds.getWidth():bounds.getHeight());
211         int xbias = (m_horiz ? 1 : 0);
212         int ybias = (m_horiz ? 0 : 1);
213         int mult = m_top ? 1 : -1;
214         float inc = (float) (m_horiz ? (bounds.getMinY()-bounds.getMaxY())
215                                      : (bounds.getMaxX()-bounds.getMinX()));
216         inc /= columns.length-1;
217         int len = columns.length;
218         
219         // perform first walk to compute max values
220
double maxValue = getPeaks();
221         float b = (float)(m_horiz ? (m_top?bounds.getMinX():bounds.getMaxX())
222                                   : (m_top?bounds.getMinY():bounds.getMaxY()));
223         Arrays.fill(baseline, b);
224         
225         m_model.setValueRange(0, maxValue, 0, maxValue);
226         
227         // perform second walk to compute polygon layout
228
Table t = (Table)m_vis.getGroup(m_group);
229         Iterator JavaDoc iter = t.tuplesReversed();
230         while ( iter.hasNext() ) {
231             VisualItem item = (VisualItem)iter.next();
232             if ( !item.isVisible() ) continue;
233             
234             float height = 0;
235             
236             for ( int i=len; --i >= 0; ) {
237                 poly[2*(len-1-i)+xbias] = min + i*inc;
238                 poly[2*(len-1-i)+ybias] = (float)baseline[i];
239             }
240             for ( int i=0; i<columns.length; ++i ) {
241                 int base = 2*(len+i);
242                 double value = item.getDouble(columns[i]);
243                 baseline[i] += mult * hgt *
244                                  MathLib.linearInterp(value,0,peaks[i]);
245                 poly[base+xbias] = min + i*inc;
246                 poly[base+ybias] = (float)baseline[i];
247                 height = Math.max(height,
248                         Math.abs(poly[2*(len-1-i)+ybias]-poly[base+ybias]));
249             }
250             if ( height < m_threshold ) {
251                 item.setVisible(false);
252             }
253
254             setX(item, null, 0);
255             setY(item, null, 0);
256             setPolygon(item, poly);
257         }
258     }
259     
260     private double getPeaks() {
261         double sum = 0;
262         
263         // first, compute max value of the current data
264
Arrays.fill(peaks, 0);
265         Iterator JavaDoc iter = m_vis.visibleItems(m_group);
266         while ( iter.hasNext() ) {
267             VisualItem item = (VisualItem)iter.next();
268             for ( int i=0; i<columns.length; ++i ) {
269                 double val = item.getDouble(columns[i]);
270                 peaks[i] += val;
271                 sum += val;
272             }
273         }
274         double max = ArrayLib.max(peaks);
275         
276         // update peaks array as needed
277
if ( !m_norm ) {
278             Arrays.fill(peaks, max);
279         }
280         
281         // adjust peaks to include padding space
282
if ( !m_norm ) {
283             for ( int i=0; i<peaks.length; ++i ) {
284                 peaks[i] += m_padding * peaks[i];
285             }
286             max += m_padding*max;
287         }
288         
289         // return max range value
290
if ( m_norm ) {
291             max = 1.0;
292         }
293         if ( Double.isNaN(max) )
294             max = 0;
295         return max;
296     }
297     
298     /**
299      * Sets the polygon values for a visual item.
300      */

301     private void setPolygon(VisualItem item, float[] poly) {
302         float[] a = getPolygon(item, m_field);
303         float[] s = getPolygon(item, m_start);
304         float[] e = getPolygon(item, m_end);
305         System.arraycopy(a, 0, s, 0, a.length);
306         System.arraycopy(poly, 0, a, 0, poly.length);
307         System.arraycopy(poly, 0, e, 0, poly.length);
308         item.setValidated(false);
309     }
310     
311     /**
312      * Get the polygon values for a visual item.
313      */

314     private float[] getPolygon(VisualItem item, String JavaDoc field) {
315         float[] poly = (float[])item.get(field);
316         if ( poly == null || poly.length < 4*columns.length ) {
317             // get oriented
318
int len = columns.length;
319             float inc = (float) (m_horiz?(bounds.getMinY()-bounds.getMaxY())
320                                         :(bounds.getMaxX()-bounds.getMinX()));
321             inc /= len-1;
322             float max = (float)
323                 (m_horiz ? (m_top?bounds.getMaxX():bounds.getMinX())
324                          : (m_top?bounds.getMinY():bounds.getMaxY()));
325             float min = (float)(m_horiz?bounds.getMaxY():bounds.getMinX());
326             int bias = (m_horiz ? 1 : 0);
327             
328             // create polygon, populate default values
329
poly = new float[4*len];
330             Arrays.fill(poly, max);
331             for ( int i=0; i<len; ++i ) {
332                 float x = i*inc + min;
333                 poly[2*(len+i) +bias] = x;
334                 poly[2*(len-1-i)+bias] = x;
335             }
336             item.set(field, poly);
337         }
338         return poly;
339     }
340     
341 } // end of class StackedAreaChart
342
Popular Tags