From c6827fe24265787def3cb16d63d9fbf583d50bd5 Mon Sep 17 00:00:00 2001 From: David Huynh Date: Mon, 24 May 2010 19:04:55 +0000 Subject: [PATCH] Fixed issue 58: Meta facet git-svn-id: http://google-refine.googlecode.com/svn/trunk@848 7d457c2a-affb-35e4-300a-418c747d4874 --- CHANGES.txt | 2 + .../util/ExpressionNominalValueGrouper.java | 65 +++++++++++-------- .../gridworks/expr/functions/FacetCount.java | 64 ++++++++++++++++++ .../gel/ControlFunctionRegistry.java | 3 + src/main/webapp/scripts/facets/list-facet.js | 50 ++++++++++++++ src/main/webapp/styles/project/browsing.css | 4 ++ 6 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/metaweb/gridworks/expr/functions/FacetCount.java diff --git a/CHANGES.txt b/CHANGES.txt index 78bc88287..f3c076f34 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,8 @@ Fixes: - Issue 46: "Array literals in GEL" - Issue 55: "Use stable sorting for text facets sorted by count" - Issue 53: "Moving the cursor inside the Text Filter box by clicking" +- Issue 58: "Meta facet" + Supported by the function facetCount() Features: - Row/record sorting (Issue 32) diff --git a/src/main/java/com/metaweb/gridworks/browsing/util/ExpressionNominalValueGrouper.java b/src/main/java/com/metaweb/gridworks/browsing/util/ExpressionNominalValueGrouper.java index 9a4c65456..ed3fa821d 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/util/ExpressionNominalValueGrouper.java +++ b/src/main/java/com/metaweb/gridworks/browsing/util/ExpressionNominalValueGrouper.java @@ -1,5 +1,6 @@ package com.metaweb.gridworks.browsing.util; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -166,35 +167,45 @@ public class ExpressionNominalValueGrouper implements RowVisitor, RecordVisitor @Override public Object eval(Project project, int rowIndex, Row row, Properties bindings) { Object value = evalRow(project, rowIndex, row, bindings); - if (value != null) { - if (value.getClass().isArray()) { - Object[] a = (Object[]) value; - for (int i = 0; i < a.length; i++) { - a[i] = getValueCount(a[i]); - } - return a; - } else if (value instanceof Collection) { - List list = ExpressionUtils.toObjectList(value); - int count = list.size(); - for (int i = 0; i < count; i++) { - list.set(i, getValueCount(list.get(i))); - } - return list; - } - } - - return getValueCount(value); + return getChoiceValueCountMultiple(value); } - protected Integer getValueCount(Object value) { - if (value == null) { - return blankCount; - } else if (ExpressionUtils.isError(value)) { - return errorCount; - } else { - return choices.get(value).count; - } - } }; } + + public Object getChoiceValueCountMultiple(Object value) { + if (value != null) { + if (value.getClass().isArray()) { + Object[] choiceValues = (Object[]) value; + List counts = new ArrayList(choiceValues.length); + + for (int i = 0; i < choiceValues.length; i++) { + counts.add(getChoiceValueCount(choiceValues[i])); + } + return counts; + } else if (value instanceof Collection) { + List choiceValues = ExpressionUtils.toObjectList(value); + List counts = new ArrayList(choiceValues.size()); + + int count = choiceValues.size(); + for (int i = 0; i < count; i++) { + counts.add(getChoiceValueCount(choiceValues.get(i))); + } + return counts; + } + } + + return getChoiceValueCount(value); + } + + public Integer getChoiceValueCount(Object choiceValue) { + if (ExpressionUtils.isError(choiceValue)) { + return errorCount; + } else if (ExpressionUtils.isNonBlankData(choiceValue)) { + IndexedNominalFacetChoice choice = choices.get(choiceValue); + return choice != null ? choice.count : 0; + } else { + return blankCount; + } + } } diff --git a/src/main/java/com/metaweb/gridworks/expr/functions/FacetCount.java b/src/main/java/com/metaweb/gridworks/expr/functions/FacetCount.java new file mode 100644 index 000000000..d1baccd69 --- /dev/null +++ b/src/main/java/com/metaweb/gridworks/expr/functions/FacetCount.java @@ -0,0 +1,64 @@ +package com.metaweb.gridworks.expr.functions; + +import java.util.Properties; + +import org.json.JSONException; +import org.json.JSONWriter; + +import com.metaweb.gridworks.browsing.Engine; +import com.metaweb.gridworks.browsing.util.ExpressionNominalValueGrouper; +import com.metaweb.gridworks.expr.EvalError; +import com.metaweb.gridworks.expr.Evaluable; +import com.metaweb.gridworks.expr.MetaParser; +import com.metaweb.gridworks.expr.ParsingException; +import com.metaweb.gridworks.gel.ControlFunctionRegistry; +import com.metaweb.gridworks.gel.Function; +import com.metaweb.gridworks.model.Column; +import com.metaweb.gridworks.model.Project; + +public class FacetCount implements Function { + + public Object call(Properties bindings, Object[] args) { + if (args.length == 3 && args[1] instanceof String && args[2] instanceof String) { + Object choiceValue = args[0]; // choice value to look up + String facetExpression = (String) args[1]; + String columnName = (String) args[2]; + + Project project = (Project) bindings.get("project"); + Column column = project.columnModel.getColumnByName(columnName); + if (column == null) { + return new EvalError("No such column named " + columnName); + } + + String key = "nominal-bin:" + facetExpression; + ExpressionNominalValueGrouper grouper = (ExpressionNominalValueGrouper) column.getPrecompute(key); + if (grouper == null) { + try { + Evaluable eval = MetaParser.parse(facetExpression); + Engine engine = new Engine(project); + + grouper = new ExpressionNominalValueGrouper(eval, columnName, column.getCellIndex()); + engine.getAllRows().accept(project, grouper); + + column.setPrecompute(key, grouper); + } catch (ParsingException e) { + return new EvalError("Error parsing facet expression " + facetExpression); + } + } + + return grouper.getChoiceValueCountMultiple(choiceValue); + } + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + + " expects a choice value, an expression as a string, and a column name"); + } + + public void write(JSONWriter writer, Properties options) + throws JSONException { + + writer.object(); + writer.key("description"); writer.value("Returns the facet count corresponding to the given choice value"); + writer.key("params"); writer.value("choiceValue, string facetExpression, string columnName"); + writer.key("returns"); writer.value("number"); + writer.endObject(); + } +} diff --git a/src/main/java/com/metaweb/gridworks/gel/ControlFunctionRegistry.java b/src/main/java/com/metaweb/gridworks/gel/ControlFunctionRegistry.java index 66189814e..430d86ebd 100644 --- a/src/main/java/com/metaweb/gridworks/gel/ControlFunctionRegistry.java +++ b/src/main/java/com/metaweb/gridworks/gel/ControlFunctionRegistry.java @@ -6,6 +6,7 @@ import java.util.Set; import java.util.Map.Entry; import com.metaweb.gridworks.expr.functions.Cross; +import com.metaweb.gridworks.expr.functions.FacetCount; import com.metaweb.gridworks.expr.functions.Get; import com.metaweb.gridworks.expr.functions.Length; import com.metaweb.gridworks.expr.functions.Slice; @@ -175,6 +176,8 @@ public class ControlFunctionRegistry { registerFunction("cross", new Cross()); + registerFunction("facetCount", new FacetCount()); + registerControl("if", new If()); registerControl("with", new With()); registerControl("forEach", new ForEach()); diff --git a/src/main/webapp/scripts/facets/list-facet.js b/src/main/webapp/scripts/facets/list-facet.js index 1116d8506..07a327589 100644 --- a/src/main/webapp/scripts/facets/list-facet.js +++ b/src/main/webapp/scripts/facets/list-facet.js @@ -245,6 +245,9 @@ ListFacet.prototype._update = function(resetScroll) { this._elmts.bodyInnerDiv.empty().append( $('
').text(this._data.error).addClass("facet-body-message")); + if (this._data.error == "Too many choices") { + this._renderBodyControls(); + } return; } @@ -324,6 +327,7 @@ ListFacet.prototype._update = function(resetScroll) { } this._elmts.bodyInnerDiv.html(html.join('')); + this._renderBodyControls(); this._elmts.bodyInnerDiv[0].scrollTop = scrollTop; var getChoice = function(elmt) { @@ -398,6 +402,52 @@ ListFacet.prototype._update = function(resetScroll) { window.setTimeout(wireEvents, 100); }; +ListFacet.prototype._renderBodyControls = function() { + var self = this; + var bodyControls = $('
') + .addClass("facet-body-controls") + .appendTo(this._elmts.bodyInnerDiv); + + $('') + .text("facet by choice counts") + .attr("href", "javascript:{}") + .addClass("action") + .appendTo(bodyControls) + .click(function() { + ui.browsingEngine.addFacet( + "range", + { + "name" : self._config.columnName, + "columnName" : self._config.columnName, + "expression" : self._getMetaExpression(), + "mode" : "range" + }, + { + } + ); + }); +}; + +ListFacet.prototype._getMetaExpression = function() { + var expression = this._config.expression; + + var language = "gel:"; + var colon = expression.indexOf(":"); + if (colon > 0) { + var l = expression.substring(0, colon + 1); + if (l == "gel:" || l == "jython:" || l == "clojure:") { + expression = expression.substring(colon + 1); + language = l; + } + } + + return language + 'facetCount(' + [ + expression, + JSON.stringify(this._config.expression), + JSON.stringify(this._config.columnName) + ].join(', ') + ')'; +} + ListFacet.prototype._doEdit = function() { new ClusteringDialog(this._config.columnName, this._config.expression); }; diff --git a/src/main/webapp/styles/project/browsing.css b/src/main/webapp/styles/project/browsing.css index acd030c9d..b9afc28ee 100644 --- a/src/main/webapp/styles/project/browsing.css +++ b/src/main/webapp/styles/project/browsing.css @@ -137,6 +137,10 @@ a.facet-title-remove:hover { height: 100%; overflow: auto; } +.facet-body-controls { + margin: 0.5em 0; + text-align: center; +} .facet-choice { padding: 2px 5px;