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