001: package prefuse.data.query;
002:
003: import java.awt.event.FocusEvent;
004: import java.awt.event.FocusListener;
005:
006: import javax.swing.JComponent;
007: import javax.swing.JSlider;
008: import javax.swing.event.ChangeEvent;
009: import javax.swing.event.ChangeListener;
010:
011: import prefuse.data.expression.ColumnExpression;
012: import prefuse.data.expression.Literal;
013: import prefuse.data.expression.RangePredicate;
014: import prefuse.data.tuple.TupleSet;
015: import prefuse.util.DataLib;
016: import prefuse.util.TypeLib;
017: import prefuse.util.ui.JRangeSlider;
018: import prefuse.util.ui.ValuedRangeModel;
019:
020: /**
021: * DynamicQueryBinding supporting queries based on a range of included
022: * data values.
023: * @author <a href="http://jheer.org">jeffrey heer</a>
024: */
025: public class RangeQueryBinding extends DynamicQueryBinding {
026:
027: private Class m_type;
028: private Listener m_lstnr;
029: private ValuedRangeModel m_model;
030: private boolean m_ordinal;
031:
032: private static FocusListener s_sliderAdj;
033:
034: /**
035: * Create a new RangeQueryBinding over the given set and data field.
036: * @param ts the TupleSet to query
037: * @param field the data field (Table column) to query
038: */
039: public RangeQueryBinding(TupleSet ts, String field) {
040: this (ts, field, false);
041: }
042:
043: /**
044: * Create a new RangeQueryBinding over the given set and data field,
045: * optionally forcing an ordinal treatment of data.
046: * @param ts the TupleSet to query
047: * @param field the data field (Table column) to query
048: * @param forceOrdinal if true, forces all items in the range to be
049: * treated in strictly ordinal fashion. That means that if the data
050: * is numerical, the quantitative nature of the data will be ignored
051: * and only the relative ordering of the numbers will matter. In terms
052: * of mechanism, this entails that a {@link ObjectRangeModel} and not
053: * a {@link NumberRangeModel} will be used to represent the data. If
054: * the argument is false, default inference mechanisms will be used.
055: */
056: public RangeQueryBinding(TupleSet ts, String field,
057: boolean forceOrdinal) {
058: super (ts, field);
059: m_type = DataLib.inferType(ts, field);
060: m_ordinal = forceOrdinal;
061: m_lstnr = new Listener();
062: initPredicate();
063: initModel();
064: }
065:
066: private void initPredicate() {
067: // determine minimum and maximum values
068: Object min = DataLib.min(m_tuples, m_field).get(m_field);
069: Object max = DataLib.max(m_tuples, m_field).get(m_field);
070:
071: // set up predicate
072: Literal left = Literal.getLiteral(min, m_type);
073: Literal right = Literal.getLiteral(max, m_type);
074: ColumnExpression ce = new ColumnExpression(m_field);
075: RangePredicate rp = new RangePredicate(ce, left, right);
076: setPredicate(rp);
077: }
078:
079: public void initModel() {
080: if (m_model != null)
081: m_model.removeChangeListener(m_lstnr);
082:
083: // set up data / selection model
084: ValuedRangeModel model = null;
085: if (TypeLib.isNumericType(m_type) && !m_ordinal) {
086: Number min = (Number) DataLib.min(m_tuples, m_field).get(
087: m_field);
088: Number max = (Number) DataLib.max(m_tuples, m_field).get(
089: m_field);
090: model = new NumberRangeModel(min, max, min, max);
091: } else {
092: model = new ObjectRangeModel(DataLib.ordinalArray(m_tuples,
093: m_field));
094: }
095: m_model = model;
096: m_model.addChangeListener(m_lstnr);
097: }
098:
099: /**
100: * Return the ValuedRangeModel constructed by this dynamic query binding.
101: * This model backs any user interface components generated by this
102: * instance.
103: * @return the ValuedRangeModel for this range query binding.
104: */
105: public ValuedRangeModel getModel() {
106: return m_model;
107: }
108:
109: /**
110: * Attempts to return the ValuedRangeModel for this binding as a
111: * NumberRangeModel. If the range model is not an instance of
112: * {@link NumberRangeModel}, a null value is returned.
113: * @return the ValuedRangeModel for this binding as a
114: * {@link NumberRangeModel}, or null if the range is not numerical.
115: */
116: public NumberRangeModel getNumberModel() {
117: return (m_model instanceof NumberRangeModel ? (NumberRangeModel) m_model
118: : null);
119: }
120:
121: /**
122: * Attempts to return the ValuedRangeModel for this binding as an
123: * ObjectRangeModel. If the range model is not an instance of
124: * {@link ObjectRangeModel}, a null value is returned.
125: * @return the ValuedRangeModel for this binding as an
126: * {@link ObjectRangeModel}, or null if the range is numerical.
127: */
128: public ObjectRangeModel getObjectModel() {
129: return (m_model instanceof ObjectRangeModel ? (ObjectRangeModel) m_model
130: : null);
131: }
132:
133: // ------------------------------------------------------------------------
134:
135: /**
136: * Create a new horizontal range slider for interacting with the query.
137: * @return a {@link prefuse.util.ui.JRangeSlider} bound to this dynamic
138: * query.
139: * @see prefuse.data.query.DynamicQueryBinding#createComponent()
140: */
141: public JComponent createComponent() {
142: return createHorizontalRangeSlider();
143: }
144:
145: /**
146: * Create a new horizontal range slider for interacting with the query.
147: * @return a {@link prefuse.util.ui.JRangeSlider} bound to this dynamic
148: * query.
149: */
150: public JRangeSlider createHorizontalRangeSlider() {
151: return createRangeSlider(JRangeSlider.HORIZONTAL,
152: JRangeSlider.LEFTRIGHT_TOPBOTTOM);
153: }
154:
155: /**
156: * Create a new vertical range slider for interacting with the query.
157: * @return a {@link prefuse.util.ui.JRangeSlider} bound to this dynamic
158: * query.
159: */
160: public JRangeSlider createVerticalRangeSlider() {
161: return createRangeSlider(JRangeSlider.VERTICAL,
162: JRangeSlider.RIGHTLEFT_BOTTOMTOP);
163: }
164:
165: /**
166: * Create a new range slider for interacting with the query, using the
167: * given orientation and direction.
168: * @param orientation the orientation (horizontal or vertical) of the
169: * slider (see {@link prefuse.util.ui.JRangeSlider})
170: * @param direction the direction (direction of data values) of the slider
171: * (see {@link prefuse.util.ui.JRangeSlider})
172: * @return a {@link prefuse.util.ui.JRangeSlider} bound to this dynamic
173: * query.
174: */
175: public JRangeSlider createRangeSlider(int orientation, int direction) {
176: return new JRangeSlider(m_model, orientation, direction);
177: }
178:
179: /**
180: * Create a new regular (non-range) slider for interacting with the query.
181: * This allows you to select a single value at a time.
182: * @return a {@link javax.swing.JSlider} bound to this dynamic query.
183: */
184: public JSlider createSlider() {
185: JSlider slider = new JSlider(m_model);
186: slider.addFocusListener(getSliderAdjuster());
187: return slider;
188: }
189:
190: private synchronized static FocusListener getSliderAdjuster() {
191: if (s_sliderAdj == null)
192: s_sliderAdj = new SliderAdjuster();
193: return s_sliderAdj;
194: }
195:
196: // ------------------------------------------------------------------------
197:
198: private static class SliderAdjuster implements FocusListener {
199: public void focusGained(FocusEvent e) {
200: ((JSlider) e.getSource()).setExtent(0);
201: }
202:
203: public void focusLost(FocusEvent e) {
204: // do nothing
205: }
206: } // end of inner class SliderAdjuster
207:
208: private class Listener implements ChangeListener {
209: public void stateChanged(ChangeEvent e) {
210: ValuedRangeModel model = (ValuedRangeModel) e.getSource();
211: Object lo = model.getLowValue();
212: Object hi = model.getHighValue();
213:
214: RangePredicate rp = (RangePredicate) m_query;
215: rp.setLeftExpression(Literal.getLiteral(lo, m_type));
216: rp.setRightExpression(Literal.getLiteral(hi, m_type));
217: }
218: } // end of inner class Listener
219:
220: } // end of class RangeQueryBinding
|