- Made removing facet a bit more interactive: the removed facet disappears right away.

- Made list facets limit themselves to only 2000 choices, so not to overload the browser.
- Made list and range facets handle errors in expressions better.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@287 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2010-03-12 20:08:50 +00:00
parent 00a81c5fc4
commit 67bac099f0
7 changed files with 217 additions and 137 deletions

View File

@ -15,6 +15,8 @@ import com.metaweb.gridworks.browsing.filters.ExpressionEqualRowFilter;
import com.metaweb.gridworks.browsing.filters.RowFilter; import com.metaweb.gridworks.browsing.filters.RowFilter;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
import com.metaweb.gridworks.expr.MetaParser; import com.metaweb.gridworks.expr.MetaParser;
import com.metaweb.gridworks.expr.ParsingException;
import com.metaweb.gridworks.model.Column;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.util.JSONUtilities; import com.metaweb.gridworks.util.JSONUtilities;
@ -31,8 +33,10 @@ public class ListFacet implements Facet {
protected String _name; protected String _name;
protected String _expression; protected String _expression;
protected String _columnName; protected String _columnName;
protected int _cellIndex; protected int _cellIndex;
protected Evaluable _eval; protected Evaluable _eval;
protected String _errorMessage;
// computed // computed
protected List<NominalFacetChoice> _choices = new LinkedList<NominalFacetChoice>(); protected List<NominalFacetChoice> _choices = new LinkedList<NominalFacetChoice>();
@ -50,25 +54,31 @@ public class ListFacet implements Facet {
writer.key("expression"); writer.value(_expression); writer.key("expression"); writer.value(_expression);
writer.key("columnName"); writer.value(_columnName); writer.key("columnName"); writer.value(_columnName);
writer.key("choices"); writer.array(); if (_errorMessage != null) {
for (NominalFacetChoice choice : _choices) { writer.key("error"); writer.value(_errorMessage);
choice.write(writer, options); } else if (_choices.size() > 2000) {
} writer.key("error"); writer.value("Too many choices.");
writer.endArray(); } else {
writer.key("choices"); writer.array();
if (!_omitBlank && (_selectBlank || _blankCount > 0)) { for (NominalFacetChoice choice : _choices) {
writer.key("blankChoice"); choice.write(writer, options);
writer.object(); }
writer.key("s"); writer.value(_selectBlank); writer.endArray();
writer.key("c"); writer.value(_blankCount);
writer.endObject(); if (!_omitBlank && (_selectBlank || _blankCount > 0)) {
} writer.key("blankChoice");
if (!_omitError && (_selectError || _errorCount > 0)) { writer.object();
writer.key("errorChoice"); writer.key("s"); writer.value(_selectBlank);
writer.object(); writer.key("c"); writer.value(_blankCount);
writer.key("s"); writer.value(_selectError); writer.endObject();
writer.key("c"); writer.value(_errorCount); }
writer.endObject(); if (!_omitError && (_selectError || _errorCount > 0)) {
writer.key("errorChoice");
writer.object();
writer.key("s"); writer.value(_selectError);
writer.key("c"); writer.value(_errorCount);
writer.endObject();
}
} }
writer.endObject(); writer.endObject();
@ -80,12 +90,22 @@ public class ListFacet implements Facet {
_columnName = o.getString("columnName"); _columnName = o.getString("columnName");
if (_columnName.length() > 0) { if (_columnName.length() > 0) {
_cellIndex = project.columnModel.getColumnByName(_columnName).getCellIndex(); Column column = project.columnModel.getColumnByName(_columnName);
if (column != null) {
_cellIndex = column.getCellIndex();
} else {
_errorMessage = "No column named " + _columnName;
}
} else { } else {
_cellIndex = -1; _cellIndex = -1;
} }
_eval = MetaParser.parse(_expression); try {
_eval = MetaParser.parse(_expression);
} catch (ParsingException e) {
_errorMessage = e.getMessage();
}
_selection.clear(); _selection.clear();
JSONArray a = o.getJSONArray("selection"); JSONArray a = o.getJSONArray("selection");
@ -112,31 +132,42 @@ public class ListFacet implements Facet {
} }
public RowFilter getRowFilter() { public RowFilter getRowFilter() {
return _selection.size() == 0 && !_selectBlank && !_selectError ? null : return
new ExpressionEqualRowFilter(_eval, _cellIndex, createMatches(), _selectBlank, _selectError); _eval == null ||
_errorMessage != null ||
(_selection.size() == 0 && !_selectBlank && !_selectError) ?
null :
new ExpressionEqualRowFilter(
_eval,
_cellIndex,
createMatches(),
_selectBlank,
_selectError);
} }
public void computeChoices(Project project, FilteredRows filteredRows) { public void computeChoices(Project project, FilteredRows filteredRows) {
ExpressionNominalRowGrouper grouper = if (_eval != null && _errorMessage == null) {
new ExpressionNominalRowGrouper(_eval, _cellIndex); ExpressionNominalRowGrouper grouper =
new ExpressionNominalRowGrouper(_eval, _cellIndex);
filteredRows.accept(project, grouper);
filteredRows.accept(project, grouper);
_choices.clear();
_choices.addAll(grouper.choices.values()); _choices.clear();
_choices.addAll(grouper.choices.values());
for (NominalFacetChoice choice : _selection) {
String valueString = choice.decoratedValue.value.toString(); for (NominalFacetChoice choice : _selection) {
if (grouper.choices.containsKey(valueString)) { String valueString = choice.decoratedValue.value.toString();
grouper.choices.get(valueString).selected = true; if (grouper.choices.containsKey(valueString)) {
} else { grouper.choices.get(valueString).selected = true;
choice.count = 0; } else {
_choices.add(choice); choice.count = 0;
_choices.add(choice);
}
} }
_blankCount = grouper.blankCount;
_errorCount = grouper.errorCount;
} }
_blankCount = grouper.blankCount;
_errorCount = grouper.errorCount;
} }
protected Object[] createMatches() { protected Object[] createMatches() {

View File

@ -11,6 +11,7 @@ import com.metaweb.gridworks.browsing.filters.ExpressionNumberComparisonRowFilte
import com.metaweb.gridworks.browsing.filters.RowFilter; import com.metaweb.gridworks.browsing.filters.RowFilter;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
import com.metaweb.gridworks.expr.MetaParser; import com.metaweb.gridworks.expr.MetaParser;
import com.metaweb.gridworks.expr.ParsingException;
import com.metaweb.gridworks.model.Column; import com.metaweb.gridworks.model.Column;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.util.JSONUtilities; import com.metaweb.gridworks.util.JSONUtilities;
@ -19,8 +20,10 @@ public class RangeFacet implements Facet {
protected String _name; protected String _name;
protected String _expression; protected String _expression;
protected String _columnName; protected String _columnName;
protected int _cellIndex; protected int _cellIndex;
protected Evaluable _eval; protected Evaluable _eval;
protected String _errorMessage;
protected String _mode; protected String _mode;
protected double _min; protected double _min;
@ -53,38 +56,41 @@ public class RangeFacet implements Facet {
writer.key("columnName"); writer.value(_columnName); writer.key("columnName"); writer.value(_columnName);
writer.key("mode"); writer.value(_mode); writer.key("mode"); writer.value(_mode);
if (!Double.isInfinite(_min) && !Double.isInfinite(_max)) { if (_errorMessage != null) {
writer.key("min"); writer.value(_min); writer.key("error"); writer.value(_errorMessage);
writer.key("max"); writer.value(_max); } else {
writer.key("step"); writer.value(_step); if (!Double.isInfinite(_min) && !Double.isInfinite(_max)) {
writer.key("min"); writer.value(_min);
writer.key("bins"); writer.array(); writer.key("max"); writer.value(_max);
for (int b : _bins) { writer.key("step"); writer.value(_step);
writer.value(b);
writer.key("bins"); writer.array();
for (int b : _bins) {
writer.value(b);
}
writer.endArray();
writer.key("baseBins"); writer.array();
for (int b : _baseBins) {
writer.value(b);
}
writer.endArray();
if ("min".equals(_mode)) {
writer.key("from"); writer.value(_from);
} else if ("max".equals(_mode)) {
writer.key("to"); writer.value(_to);
} else {
writer.key("from"); writer.value(_from);
writer.key("to"); writer.value(_to);
}
} }
writer.endArray();
writer.key("baseBins"); writer.array(); writer.key("numericCount"); writer.value(_numericCount);
for (int b : _baseBins) { writer.key("nonNumericCount"); writer.value(_nonNumericCount);
writer.value(b); writer.key("blankCount"); writer.value(_blankCount);
} writer.key("errorCount"); writer.value(_errorCount);
writer.endArray();
if ("min".equals(_mode)) {
writer.key("from"); writer.value(_from);
} else if ("max".equals(_mode)) {
writer.key("to"); writer.value(_to);
} else {
writer.key("from"); writer.value(_from);
writer.key("to"); writer.value(_to);
}
} }
writer.key("numericCount"); writer.value(_numericCount);
writer.key("nonNumericCount"); writer.value(_nonNumericCount);
writer.key("blankCount"); writer.value(_blankCount);
writer.key("errorCount"); writer.value(_errorCount);
writer.endObject(); writer.endObject();
} }
@ -92,9 +98,23 @@ public class RangeFacet implements Facet {
_name = o.getString("name"); _name = o.getString("name");
_expression = o.getString("expression"); _expression = o.getString("expression");
_columnName = o.getString("columnName"); _columnName = o.getString("columnName");
_cellIndex = project.columnModel.getColumnByName(_columnName).getCellIndex();
_eval = MetaParser.parse(_expression); if (_columnName.length() > 0) {
Column column = project.columnModel.getColumnByName(_columnName);
if (column != null) {
_cellIndex = column.getCellIndex();
} else {
_errorMessage = "No column named " + _columnName;
}
} else {
_cellIndex = -1;
}
try {
_eval = MetaParser.parse(_expression);
} catch (ParsingException e) {
_errorMessage = e.getMessage();
}
_mode = o.getString("mode"); _mode = o.getString("mode");
if ("min".equals(_mode)) { if ("min".equals(_mode)) {
@ -126,7 +146,7 @@ public class RangeFacet implements Facet {
} }
public RowFilter getRowFilter() { public RowFilter getRowFilter() {
if (_selected) { if (_eval != null && _errorMessage == null && _selected) {
if ("min".equals(_mode)) { if ("min".equals(_mode)) {
return new ExpressionNumberComparisonRowFilter( return new ExpressionNumberComparisonRowFilter(
_eval, _cellIndex, _selectNumeric, _selectNonNumeric, _selectBlank, _selectError) { _eval, _cellIndex, _selectNumeric, _selectNonNumeric, _selectBlank, _selectError) {
@ -158,37 +178,39 @@ public class RangeFacet implements Facet {
} }
public void computeChoices(Project project, FilteredRows filteredRows) { public void computeChoices(Project project, FilteredRows filteredRows) {
Column column = project.columnModel.getColumnByCellIndex(_cellIndex); if (_eval != null && _errorMessage == null) {
Column column = project.columnModel.getColumnByCellIndex(_cellIndex);
String key = "numeric-bin:" + _expression;
NumericBinIndex index = (NumericBinIndex) column.getPrecompute(key); String key = "numeric-bin:" + _expression;
if (index == null) { NumericBinIndex index = (NumericBinIndex) column.getPrecompute(key);
index = new NumericBinIndex(project, _cellIndex, _eval); if (index == null) {
column.setPrecompute(key, index); index = new NumericBinIndex(project, _cellIndex, _eval);
column.setPrecompute(key, index);
}
_min = index.getMin();
_max = index.getMax();
_step = index.getStep();
_baseBins = index.getBins();
if (_selected) {
_from = Math.max(_from, _min);
_to = Math.min(_to, _max);
} else {
_from = _min;
_to = _max;
}
ExpressionNumericRowBinner binner =
new ExpressionNumericRowBinner(_eval, _cellIndex, index);
filteredRows.accept(project, binner);
_bins = binner.bins;
_numericCount = binner.numericCount;
_nonNumericCount = binner.nonNumericCount;
_blankCount = binner.blankCount;
_errorCount = binner.errorCount;
} }
_min = index.getMin();
_max = index.getMax();
_step = index.getStep();
_baseBins = index.getBins();
if (_selected) {
_from = Math.max(_from, _min);
_to = Math.min(_to, _max);
} else {
_from = _min;
_to = _max;
}
ExpressionNumericRowBinner binner =
new ExpressionNumericRowBinner(_eval, _cellIndex, index);
filteredRows.accept(project, binner);
_bins = binner.bins;
_numericCount = binner.numericCount;
_nonNumericCount = binner.nonNumericCount;
_blankCount = binner.blankCount;
_errorCount = binner.errorCount;
} }
} }

View File

@ -106,7 +106,7 @@ public class Parser {
protected Evaluable parseFactor() throws ParsingException { protected Evaluable parseFactor() throws ParsingException {
if (_token == null) { if (_token == null) {
throw makeException("Expression ends too early"); throw makeException("Expecting something more at end of expression");
} }
Evaluable eval = null; Evaluable eval = null;
@ -170,7 +170,7 @@ public class Parser {
throw makeException("Missing )"); throw makeException("Missing )");
} }
} else { } else {
throw makeException("Missing number, string, identifier, or parenthesized expression"); throw makeException("Missing number, string, identifier, regex, or parenthesized expression");
} }
while (_token != null) { while (_token != null) {

View File

@ -62,19 +62,21 @@ ListFacet.prototype.hasSelection = function() {
ListFacet.prototype.updateState = function(data) { ListFacet.prototype.updateState = function(data) {
this._data = data; this._data = data;
var selection = []; if ("choices" in data) {
var choices = data.choices; var selection = [];
for (var i = 0; i < choices.length; i++) { var choices = data.choices;
var choice = choices[i]; for (var i = 0; i < choices.length; i++) {
if (choice.s) { var choice = choices[i];
selection.push(choice); if (choice.s) {
selection.push(choice);
}
} }
} this._selection = selection;
this._selection = selection; this._reSortChoices();
this._reSortChoices();
this._blankChoice = data.blankChoice || null; this._blankChoice = data.blankChoice || null;
this._errorChoice = data.errorChoice || null; this._errorChoice = data.errorChoice || null;
}
this.render(); this.render();
}; };
@ -117,8 +119,11 @@ ListFacet.prototype.render = function() {
} }
if (this._data == null) { if (this._data == null) {
$('<div>').text("Loading...").addClass("facet-body-message").appendTo(bodyDiv);
bodyDiv.appendTo(container);
} else if ("error" in this._data) {
$('<div>').text(this._data.error).addClass("facet-body-message").appendTo(bodyDiv);
bodyDiv.appendTo(container); bodyDiv.appendTo(container);
bodyDiv.html("Loading...");
} else { } else {
var selectionCount = this._selection.length var selectionCount = this._selection.length
+ (this._blankChoice != null && this._blankChoice.s ? 1 : 0) + (this._blankChoice != null && this._blankChoice.s ? 1 : 0)

View File

@ -138,14 +138,11 @@ RangeFacet.prototype._initializeUI = function() {
var bodyDiv = $('<div></div>').addClass("facet-range-body").appendTo(container); var bodyDiv = $('<div></div>').addClass("facet-range-body").appendTo(container);
if (this._error) { this._messageDiv = $('<div>').text("Loading...").addClass("facet-range-message").appendTo(bodyDiv);
return; this._histogramDiv = $('<div>').addClass("facet-range-histogram").appendTo(bodyDiv);
} this._sliderDiv = $('<div>').addClass("facet-range-slider").appendTo(bodyDiv);
this._statusDiv = $('<div>').addClass("facet-range-status").appendTo(bodyDiv);
this._histogramDiv = $('<div></div>').addClass("facet-range-histogram").appendTo(bodyDiv); this._otherChoicesDiv = $('<div>').addClass("facet-range-other-choices").appendTo(bodyDiv);
this._sliderDiv = $('<div></div>').addClass("facet-range-slider").appendTo(bodyDiv);
this._statusDiv = $('<div></div>').addClass("facet-range-status").appendTo(bodyDiv);
this._otherChoicesDiv = $('<div></div>').addClass("facet-range-other-choices").appendTo(bodyDiv);
var onSlide = function(event, ui) { var onSlide = function(event, ui) {
switch (self._config.mode) { switch (self._config.mode) {
@ -166,34 +163,27 @@ RangeFacet.prototype._initializeUI = function() {
self._updateRest(); self._updateRest();
}; };
var sliderConfig = { var sliderConfig = {
range: "max",
min: this._config.min, min: this._config.min,
max: this._config.max, max: this._config.max,
value: 2,
stop: onStop, stop: onStop,
slide: onSlide slide: onSlide
}; };
if ("step" in this._config) {
sliderConfig.step = this._config.step;
}
switch (this._config.mode) { switch (this._config.mode) {
case "min": case "min":
sliderConfig.range = "max"; sliderConfig.range = "max";
sliderConfig.value = this._from; sliderConfig.value = this._config.min;
break; break;
case "max": case "max":
sliderConfig.range = "min"; sliderConfig.range = "min";
sliderConfig.value = this._to; sliderConfig.value = this._config.max;
break; break;
default: default:
sliderConfig.range = true; sliderConfig.range = true;
sliderConfig.values = [ this._from, this._to ]; sliderConfig.values = [ this._config.min, this._config.max ];
} }
this._sliderDiv.slider(sliderConfig); this._sliderDiv.slider(sliderConfig);
this._setRangeIndicators();
this._renderOtherChoices();
}; };
RangeFacet.prototype._renderOtherChoices = function() { RangeFacet.prototype._renderOtherChoices = function() {
@ -276,6 +266,8 @@ RangeFacet.prototype._setRangeIndicators = function() {
RangeFacet.prototype.updateState = function(data) { RangeFacet.prototype.updateState = function(data) {
if ("min" in data && "max" in data) { if ("min" in data && "max" in data) {
this._error = false;
this._config.min = data.min; this._config.min = data.min;
this._config.max = data.max; this._config.max = data.max;
this._config.step = data.step; this._config.step = data.step;
@ -304,6 +296,7 @@ RangeFacet.prototype.updateState = function(data) {
this._errorCount = data.errorCount; this._errorCount = data.errorCount;
} else { } else {
this._error = true; this._error = true;
this._errorMessage = "error" in data ? data.error : "Unknown error.";
} }
this.render(); this.render();
@ -314,10 +307,22 @@ RangeFacet.prototype.render = function() {
this._initializeUI(); this._initializeUI();
this._initializedUI = true; this._initializedUI = true;
} }
if (this._error) { if (this._error) {
this._messageDiv.text(this._errorMessage).show();
this._sliderDiv.hide();
this._histogramDiv.hide();
this._statusDiv.hide();
this._otherChoicesDiv.hide();
return; return;
} }
this._messageDiv.hide();
this._sliderDiv.show();
this._histogramDiv.show();
this._statusDiv.show();
this._otherChoicesDiv.show();
this._sliderDiv.slider("option", "min", this._config.min); this._sliderDiv.slider("option", "min", this._config.min);
this._sliderDiv.slider("option", "max", this._config.max); this._sliderDiv.slider("option", "max", this._config.max);
this._sliderDiv.slider("option", "step", this._config.step); this._sliderDiv.slider("option", "step", this._config.step);

View File

@ -119,8 +119,17 @@ BrowsingEngine.prototype.removeFacet = function(facet) {
var update = facet.hasSelection(); var update = facet.hasSelection();
for (var i = this._facets.length - 1;i >= 0; i--) { for (var i = this._facets.length - 1;i >= 0; i--) {
if (this._facets[i].facet === facet) { if (this._facets[i].facet === facet) {
this._facets[i].elmt.remove(); var elmt = this._facets[i].elmt;
this._facets.splice(i, 1); this._facets.splice(i, 1);
// This makes really big facet disappear right away. If you just call remove()
// then it takes a while for all the event handlers to get unwired, and the UI
// appear frozen.
elmt.hide();
window.setTimeout(function() {
elmt.remove();
}, 300);
break; break;
} }
} }

View File

@ -37,6 +37,10 @@ li.facet-container {
border: 1px solid #ccc; border: 1px solid #ccc;
padding: 1px; padding: 1px;
} }
.facet-body-message {
margin: 1em;
color: #f88;
}
.facet-body-scrollable { .facet-body-scrollable {
height: 20em; height: 20em;
overflow: auto; overflow: auto;
@ -113,6 +117,10 @@ img.facet-choice-link {
border: 1px solid #ccc; border: 1px solid #ccc;
padding: 15px; padding: 15px;
} }
.facet-range-message {
margin: 1em;
color: #f88;
}
.facet-range-histogram { .facet-range-histogram {
margin: 10px 4px; margin: 10px 4px;
overflow: hidden; overflow: hidden;