001: package prefuse.action.layout;
002:
003: import java.awt.geom.Rectangle2D;
004: import java.util.Iterator;
005:
006: import prefuse.Constants;
007: import prefuse.data.Table;
008: import prefuse.data.Tuple;
009: import prefuse.data.expression.Predicate;
010: import prefuse.data.query.NumberRangeModel;
011: import prefuse.data.query.ObjectRangeModel;
012: import prefuse.data.tuple.TupleSet;
013: import prefuse.util.DataLib;
014: import prefuse.util.MathLib;
015: import prefuse.util.ui.ValuedRangeModel;
016: import prefuse.visual.VisualItem;
017:
018: /**
019: * Layout Action that assigns positions along a single dimension (either x or
020: * y) according to a specified data field. By default, the range of values
021: * along the axis is automatically determined by the minimum and maximum
022: * values of the data field. The range bounds can be manually set using the
023: * {@link #setRangeModel(ValuedRangeModel)} method. Also, the set of items
024: * processed by this layout can be filtered by providing a filtering
025: * predicate (@link #setFilter(Predicate)).
026: *
027: * @author <a href="http://jheer.org">jeffrey heer</a>
028: */
029: public class AxisLayout extends Layout {
030:
031: private String m_field;
032: private int m_scale = Constants.LINEAR_SCALE;
033: private int m_axis = Constants.X_AXIS;
034: private int m_type = Constants.UNKNOWN;
035:
036: // visible region of the layout (in item coordinates)
037: // if false, the table will be consulted
038: private boolean m_modelSet = false;
039: private ValuedRangeModel m_model = null;
040: private Predicate m_filter = null;
041:
042: // screen coordinate range
043: private double m_min;
044: private double m_range;
045:
046: // value range / distribution
047: private double[] m_dist = new double[2];
048:
049: /**
050: * Create a new AxisLayout. Defaults to using the x-axis.
051: * @param group the data group to layout
052: * @param field the data field upon which to base the layout
053: */
054: public AxisLayout(String group, String field) {
055: super (group);
056: m_field = field;
057: }
058:
059: /**
060: * Create a new AxisLayout.
061: * @param group the data group to layout
062: * @param field the data field upon which to base the layout
063: * @param axis the axis type, either {@link prefuse.Constants#X_AXIS}
064: * or {@link prefuse.Constants#Y_AXIS}.
065: */
066: public AxisLayout(String group, String field, int axis) {
067: this (group, field);
068: setAxis(axis);
069: }
070:
071: /**
072: * Create a new AxisLayout.
073: * @param group the data group to layout
074: * @param field the data field upon which to base the layout
075: * @param axis the axis type, either {@link prefuse.Constants#X_AXIS}
076: * or {@link prefuse.Constants#Y_AXIS}.
077: * @param filter an optional predicate filter for limiting which items
078: * to layout.
079: */
080: public AxisLayout(String group, String field, int axis,
081: Predicate filter) {
082: this (group, field, axis);
083: setFilter(filter);
084: }
085:
086: // ------------------------------------------------------------------------
087:
088: /**
089: * Set the data field used by this axis layout action. The values of the
090: * data field will determine the position of items along the axis. Note
091: * that this method does not affect the other parameters of this action. In
092: * particular, clients that have provided a custom range model for
093: * setting the axis range may need to appropriately update the model
094: * setting for use with the new data field setting.
095: * @param field the name of the data field that determines the layout
096: */
097: public void setDataField(String field) {
098: m_field = field;
099: if (!m_modelSet)
100: m_model = null;
101: }
102:
103: /**
104: * Get the data field used by this axis layout action. The values of the
105: * data field determine the position of items along the axis.
106: * @return the name of the data field that determines the layout
107: */
108: public String getDataField() {
109: return m_field;
110: }
111:
112: /**
113: * Set the range model determing the span of the axis. This model controls
114: * the minimum and maximum values of the layout, as provided by the
115: * {@link prefuse.util.ui.ValuedRangeModel#getLowValue()} and
116: * {@link prefuse.util.ui.ValuedRangeModel#getHighValue()} methods.
117: * @param model the range model for the axis.
118: */
119: public void setRangeModel(ValuedRangeModel model) {
120: m_model = model;
121: m_modelSet = (model != null);
122: }
123:
124: /**
125: * Get the range model determing the span of the axis. This model controls
126: * the minimum and maximum values of the layout, as provided by the
127: * {@link prefuse.util.ui.ValuedRangeModel#getLowValue()} and
128: * {@link prefuse.util.ui.ValuedRangeModel#getHighValue()} methods.
129: * @return the range model for the axis.
130: */
131: public ValuedRangeModel getRangeModel() {
132: return m_model;
133: }
134:
135: /**
136: * Set a predicate filter to limit which items are considered for layout.
137: * Only items for which the predicate returns a true value are included
138: * in the layout computation.
139: * @param filter the predicate filter to use. If null, no filtering
140: * will be performed.
141: */
142: public void setFilter(Predicate filter) {
143: m_filter = filter;
144: }
145:
146: /**
147: * Get the predicate filter to limit which items are considered for layout.
148: * Only items for which the predicate returns a true value are included
149: * in the layout computation.
150: * @return the predicate filter used by this layout. If null, no filtering
151: * is performed.
152: */
153: public Predicate getFilter() {
154: return m_filter;
155: }
156:
157: // ------------------------------------------------------------------------
158:
159: /**
160: * Returns the scale type used for the axis. This setting only applies
161: * for numerical data types (i.e., when axis values are from a
162: * <code>NumberValuedRange</code>).
163: * @return the scale type. One of
164: * {@link prefuse.Constants#LINEAR_SCALE},
165: * {@link prefuse.Constants#SQRT_SCALE}, or
166: * {@link Constants#LOG_SCALE}.
167: */
168: public int getScale() {
169: return m_scale;
170: }
171:
172: /**
173: * Sets the scale type used for the axis. This setting only applies
174: * for numerical data types (i.e., when axis values are from a
175: * <code>NumberValuedRange</code>).
176: * @param scale the scale type. One of
177: * {@link prefuse.Constants#LINEAR_SCALE},
178: * {@link prefuse.Constants#SQRT_SCALE}, or
179: * {@link Constants#LOG_SCALE}.
180: */
181: public void setScale(int scale) {
182: if (scale < 0 || scale >= Constants.SCALE_COUNT)
183: throw new IllegalArgumentException(
184: "Unrecognized scale value: " + scale);
185: m_scale = scale;
186: }
187:
188: /**
189: * Return the axis type of this layout, either
190: * {@link prefuse.Constants#X_AXIS} or {@link prefuse.Constants#Y_AXIS}.
191: * @return the axis type of this layout.
192: */
193: public int getAxis() {
194: return m_axis;
195: }
196:
197: /**
198: * Set the axis type of this layout.
199: * @param axis the axis type to use for this layout, either
200: * {@link prefuse.Constants#X_AXIS} or {@link prefuse.Constants#Y_AXIS}.
201: */
202: public void setAxis(int axis) {
203: if (axis < 0 || axis >= Constants.AXIS_COUNT)
204: throw new IllegalArgumentException(
205: "Unrecognized axis value: " + axis);
206: m_axis = axis;
207: }
208:
209: /**
210: * Return the data type used by this layout. This value is one of
211: * {@link prefuse.Constants#NOMINAL}, {@link prefuse.Constants#ORDINAL},
212: * {@link prefuse.Constants#NUMERICAL}, or
213: * {@link prefuse.Constants#UNKNOWN}.
214: * @return the data type used by this layout
215: */
216: public int getDataType() {
217: return m_type;
218: }
219:
220: /**
221: * Set the data type used by this layout.
222: * @param type the data type used by this layout, one of
223: * {@link prefuse.Constants#NOMINAL}, {@link prefuse.Constants#ORDINAL},
224: * {@link prefuse.Constants#NUMERICAL}, or
225: * {@link prefuse.Constants#UNKNOWN}.
226: */
227: public void setDataType(int type) {
228: if (type < 0 || type >= Constants.DATATYPE_COUNT)
229: throw new IllegalArgumentException(
230: "Unrecognized data type value: " + type);
231: m_type = type;
232: }
233:
234: // ------------------------------------------------------------------------
235:
236: /**
237: * @see prefuse.action.Action#run(double)
238: */
239: public void run(double frac) {
240: TupleSet ts = m_vis.getGroup(m_group);
241: setMinMax();
242:
243: switch (getDataType(ts)) {
244: case Constants.NUMERICAL:
245: numericalLayout(ts);
246: break;
247: default:
248: ordinalLayout(ts);
249: }
250: }
251:
252: /**
253: * Retrieve the data type.
254: */
255: protected int getDataType(TupleSet ts) {
256: if (m_type == Constants.UNKNOWN) {
257: boolean numbers = true;
258: if (ts instanceof Table) {
259: numbers = ((Table) ts).canGetDouble(m_field);
260: } else {
261: for (Iterator it = ts.tuples(); it.hasNext();) {
262: if (!((Tuple) it.next()).canGetDouble(m_field)) {
263: numbers = false;
264: break;
265: }
266: }
267: }
268: if (numbers) {
269: return Constants.NUMERICAL;
270: } else {
271: return Constants.ORDINAL;
272: }
273: } else {
274: return m_type;
275: }
276: }
277:
278: /**
279: * Set the minimum and maximum pixel values.
280: */
281: private void setMinMax() {
282: Rectangle2D b = getLayoutBounds();
283: if (m_axis == Constants.X_AXIS) {
284: m_min = b.getMinX();
285: m_range = b.getMaxX() - m_min;
286: } else {
287: m_min = b.getMaxY();
288: m_range = b.getMinY() - m_min;
289: }
290: }
291:
292: /**
293: * Set the layout position of an item.
294: */
295: protected void set(VisualItem item, double frac) {
296: double xOrY = m_min + frac * m_range;
297: if (m_axis == Constants.X_AXIS) {
298: setX(item, null, xOrY);
299: } else {
300: setY(item, null, xOrY);
301: }
302: }
303:
304: /**
305: * Compute a quantitative axis layout.
306: */
307: protected void numericalLayout(TupleSet ts) {
308: if (!m_modelSet) {
309: m_dist[0] = DataLib.min(ts, m_field).getDouble(m_field);
310: m_dist[1] = DataLib.max(ts, m_field).getDouble(m_field);
311:
312: double lo = m_dist[0], hi = m_dist[1];
313: if (m_model == null) {
314: m_model = new NumberRangeModel(lo, hi, lo, hi);
315: } else {
316: ((NumberRangeModel) m_model).setValueRange(lo, hi, lo,
317: hi);
318: }
319: } else {
320: m_dist[0] = ((Number) m_model.getLowValue()).doubleValue();
321: m_dist[1] = ((Number) m_model.getHighValue()).doubleValue();
322: }
323:
324: Iterator iter = m_vis.items(m_group, m_filter);
325: while (iter.hasNext()) {
326: VisualItem item = (VisualItem) iter.next();
327: double v = item.getDouble(m_field);
328: double f = MathLib.interp(m_scale, v, m_dist);
329: set(item, f);
330: }
331: }
332:
333: /**
334: * Compute an ordinal axis layout.
335: */
336: protected void ordinalLayout(TupleSet ts) {
337: if (!m_modelSet) {
338: Object[] array = DataLib.ordinalArray(ts, m_field);
339:
340: if (m_model == null) {
341: m_model = new ObjectRangeModel(array);
342: } else {
343: ((ObjectRangeModel) m_model).setValueRange(array);
344: }
345: }
346:
347: ObjectRangeModel model = (ObjectRangeModel) m_model;
348: int start = model.getValue();
349: int end = start + model.getExtent();
350: double total = (double) (end - start);
351:
352: Iterator iter = m_vis.items(m_group, m_filter);
353: while (iter.hasNext()) {
354: VisualItem item = (VisualItem) iter.next();
355: int order = model.getIndex(item.get(m_field)) - start;
356: set(item, order / total);
357: }
358: }
359:
360: } // end of class AxisLayout
|