package prefuse.data.query; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import javax.swing.JComponent; import javax.swing.JSlider; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import prefuse.data.expression.ColumnExpression; import prefuse.data.expression.Literal; import prefuse.data.expression.RangePredicate; import prefuse.data.tuple.TupleSet; import prefuse.util.DataLib; import prefuse.util.TypeLib; import prefuse.util.ui.JRangeSlider; import prefuse.util.ui.ValuedRangeModel; /** * DynamicQueryBinding supporting queries based on a range of included * data values. * @author <a href="http://jheer.org">jeffrey heer</a> */ public class RangeQueryBinding extends DynamicQueryBinding { private Class m_type; private Listener m_lstnr; private ValuedRangeModel m_model; private boolean m_ordinal; private static FocusListener s_sliderAdj; /** * Create a new RangeQueryBinding over the given set and data field. * @param ts the TupleSet to query * @param field the data field (Table column) to query */ public RangeQueryBinding(TupleSet ts, String field) { this(ts, field, false); } /** * Create a new RangeQueryBinding over the given set and data field, * optionally forcing an ordinal treatment of data. * @param ts the TupleSet to query * @param field the data field (Table column) to query * @param forceOrdinal if true, forces all items in the range to be * treated in strictly ordinal fashion. That means that if the data * is numerical, the quantitative nature of the data will be ignored * and only the relative ordering of the numbers will matter. In terms * of mechanism, this entails that a {@link ObjectRangeModel} and not * a {@link NumberRangeModel} will be used to represent the data. If * the argument is false, default inference mechanisms will be used. */ public RangeQueryBinding(TupleSet ts, String field, boolean forceOrdinal) { super(ts, field); m_type = DataLib.inferType(ts, field); m_ordinal = forceOrdinal; m_lstnr = new Listener(); initPredicate(); initModel(); } private void initPredicate() { // determine minimum and maximum values Object min = DataLib.min(m_tuples, m_field).get(m_field); Object max = DataLib.max(m_tuples, m_field).get(m_field); // set up predicate Literal left = Literal.getLiteral(min, m_type); Literal right = Literal.getLiteral(max, m_type); ColumnExpression ce = new ColumnExpression(m_field); RangePredicate rp = new RangePredicate(ce, left, right); setPredicate(rp); } public void initModel() { if ( m_model != null ) m_model.removeChangeListener(m_lstnr); // set up data / selection model ValuedRangeModel model = null; if ( TypeLib.isNumericType(m_type) && !m_ordinal ) { Number min = (Number)DataLib.min(m_tuples, m_field).get(m_field); Number max = (Number)DataLib.max(m_tuples, m_field).get(m_field); model = new NumberRangeModel(min, max, min, max); } else { model = new ObjectRangeModel( DataLib.ordinalArray(m_tuples, m_field)); } m_model = model; m_model.addChangeListener(m_lstnr); } /** * Return the ValuedRangeModel constructed by this dynamic query binding. * This model backs any user interface components generated by this * instance. * @return the ValuedRangeModel for this range query binding. */ public ValuedRangeModel getModel() { return m_model; } /** * Attempts to return the ValuedRangeModel for this binding as a * NumberRangeModel. If the range model is not an instance of * {@link NumberRangeModel}, a null value is returned. * @return the ValuedRangeModel for this binding as a * {@link NumberRangeModel}, or null if the range is not numerical. */ public NumberRangeModel getNumberModel() { return (m_model instanceof NumberRangeModel ? (NumberRangeModel)m_model : null); } /** * Attempts to return the ValuedRangeModel for this binding as an * ObjectRangeModel. If the range model is not an instance of * {@link ObjectRangeModel}, a null value is returned. * @return the ValuedRangeModel for this binding as an * {@link ObjectRangeModel}, or null if the range is numerical. */ public ObjectRangeModel getObjectModel() { return (m_model instanceof ObjectRangeModel ? (ObjectRangeModel)m_model : null); } // ------------------------------------------------------------------------ /** * Create a new horizontal range slider for interacting with the query. * @return a {@link prefuse.util.ui.JRangeSlider} bound to this dynamic * query. * @see prefuse.data.query.DynamicQueryBinding#createComponent() */ public JComponent createComponent() { return createHorizontalRangeSlider(); } /** * Create a new horizontal range slider for interacting with the query. * @return a {@link prefuse.util.ui.JRangeSlider} bound to this dynamic * query. */ public JRangeSlider createHorizontalRangeSlider() { return createRangeSlider(JRangeSlider.HORIZONTAL, JRangeSlider.LEFTRIGHT_TOPBOTTOM); } /** * Create a new vertical range slider for interacting with the query. * @return a {@link prefuse.util.ui.JRangeSlider} bound to this dynamic * query. */ public JRangeSlider createVerticalRangeSlider() { return createRangeSlider(JRangeSlider.VERTICAL, JRangeSlider.RIGHTLEFT_BOTTOMTOP); } /** * Create a new range slider for interacting with the query, using the * given orientation and direction. * @param orientation the orientation (horizontal or vertical) of the * slider (see {@link prefuse.util.ui.JRangeSlider}) * @param direction the direction (direction of data values) of the slider * (see {@link prefuse.util.ui.JRangeSlider}) * @return a {@link prefuse.util.ui.JRangeSlider} bound to this dynamic * query. */ public JRangeSlider createRangeSlider(int orientation, int direction) { return new JRangeSlider(m_model, orientation, direction); } /** * Create a new regular (non-range) slider for interacting with the query. * This allows you to select a single value at a time. * @return a {@link javax.swing.JSlider} bound to this dynamic query. */ public JSlider createSlider() { JSlider slider = new JSlider(m_model); slider.addFocusListener(getSliderAdjuster()); return slider; } private synchronized static FocusListener getSliderAdjuster() { if ( s_sliderAdj == null ) s_sliderAdj = new SliderAdjuster(); return s_sliderAdj; } // ------------------------------------------------------------------------ private static class SliderAdjuster implements FocusListener { public void focusGained(FocusEvent e) { ((JSlider)e.getSource()).setExtent(0); } public void focusLost(FocusEvent e) { // do nothing } } // end of inner class SliderAdjuster private class Listener implements ChangeListener { public void stateChanged(ChangeEvent e) { ValuedRangeModel model = (ValuedRangeModel)e.getSource(); Object lo = model.getLowValue(); Object hi = model.getHighValue(); RangePredicate rp = (RangePredicate)m_query; rp.setLeftExpression(Literal.getLiteral(lo, m_type)); rp.setRightExpression(Literal.getLiteral(hi, m_type)); } } // end of inner class Listener } // end of class RangeQueryBinding