Render contextual rows when filtered.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@51 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2010-02-05 23:05:00 +00:00
parent 755b01c2c4
commit 98a16ca500
19 changed files with 228 additions and 38 deletions

View File

@ -9,6 +9,11 @@ import com.metaweb.gridworks.model.Row;
public class ConjunctiveFilteredRows implements FilteredRows { public class ConjunctiveFilteredRows implements FilteredRows {
final protected List<RowFilter> _rowFilters = new LinkedList<RowFilter>(); final protected List<RowFilter> _rowFilters = new LinkedList<RowFilter>();
final protected boolean _contextual;
public ConjunctiveFilteredRows(boolean contextual) {
_contextual = contextual;
}
public void add(RowFilter rowFilter) { public void add(RowFilter rowFilter) {
_rowFilters.add(rowFilter); _rowFilters.add(rowFilter);
@ -16,6 +21,14 @@ public class ConjunctiveFilteredRows implements FilteredRows {
@Override @Override
public void accept(Project project, RowVisitor visitor) { public void accept(Project project, RowVisitor visitor) {
if (_contextual) {
contextualAccept(project, visitor);
} else {
simpleAccept(project, visitor);
}
}
protected void simpleAccept(Project project, RowVisitor visitor) {
for (int i = 0; i < project.rows.size(); i++) { for (int i = 0; i < project.rows.size(); i++) {
Row row = project.rows.get(i); Row row = project.rows.get(i);
@ -28,7 +41,42 @@ public class ConjunctiveFilteredRows implements FilteredRows {
} }
if (ok) { if (ok) {
visitor.visit(project, i, row); visitor.visit(project, i, row, false);
}
}
}
protected void contextualAccept(Project project, RowVisitor visitor) {
int lastVisitedRow = -1;
for (int i = 0; i < project.rows.size(); i++) {
Row row = project.rows.get(i);
boolean ok = true;
for (RowFilter rowFilter : _rowFilters) {
if (!rowFilter.filterRow(project, i, row)) {
ok = false;
break;
}
}
if (ok) {
if (row.contextRows != null && lastVisitedRow < i - 1) {
for (int contextRowIndex : row.contextRows) {
if (contextRowIndex > lastVisitedRow) {
visitor.visit(
project,
contextRowIndex,
project.rows.get(contextRowIndex),
true
);
lastVisitedRow = contextRowIndex;
}
}
}
visitor.visit(project, i, row, false);
lastVisitedRow = i;
} }
} }
} }

View File

@ -25,12 +25,12 @@ public class Engine implements Jsonizable {
_project = project; _project = project;
} }
public FilteredRows getAllFilteredRows() { public FilteredRows getAllFilteredRows(boolean contextual) {
return getFilteredRows(null); return getFilteredRows(null, contextual);
} }
public FilteredRows getFilteredRows(Facet except) { public FilteredRows getFilteredRows(Facet except, boolean contextual) {
ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows(); ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows(contextual);
for (Facet facet : _facets) { for (Facet facet : _facets) {
if (facet != except) { if (facet != except) {
RowFilter rowFilter = facet.getRowFilter(); RowFilter rowFilter = facet.getRowFilter();
@ -68,7 +68,7 @@ public class Engine implements Jsonizable {
public void computeFacets() throws JSONException { public void computeFacets() throws JSONException {
for (Facet facet : _facets) { for (Facet facet : _facets) {
FilteredRows filteredRows = getFilteredRows(facet); FilteredRows filteredRows = getFilteredRows(facet, false);
facet.computeChoices(_project, filteredRows); facet.computeChoices(_project, filteredRows);
} }

View File

@ -4,6 +4,6 @@ import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row; import com.metaweb.gridworks.model.Row;
public interface RowVisitor { public interface RowVisitor {
public boolean visit(Project project, int rowIndex, Row row); public boolean visit(Project project, int rowIndex, Row row, boolean contextual);
} }

View File

@ -24,7 +24,7 @@ public class ExpressionNominalRowGrouper implements RowVisitor {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
if (_cellIndex < row.cells.size()) { if (_cellIndex < row.cells.size()) {
Cell cell = row.cells.get(_cellIndex); Cell cell = row.cells.get(_cellIndex);
if (cell != null) { if (cell != null) {

View File

@ -24,7 +24,7 @@ public class ExpressionNumericRowBinner implements RowVisitor {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
if (_cellIndex < row.cells.size()) { if (_cellIndex < row.cells.size()) {
Cell cell = row.cells.get(_cellIndex); Cell cell = row.cells.get(_cellIndex);
if (cell != null) { if (cell != null) {

View File

@ -51,7 +51,7 @@ public class ExportRowsCommand extends Command {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
boolean first = true; boolean first = true;
for (Column column : project.columnModel.columns) { for (Column column : project.columnModel.columns) {
if (first) { if (first) {
@ -74,7 +74,7 @@ public class ExportRowsCommand extends Command {
} }
}.init(writer); }.init(writer);
FilteredRows filteredRows = engine.getAllFilteredRows(); FilteredRows filteredRows = engine.getAllFilteredRows(true);
filteredRows.accept(project, visitor); filteredRows.accept(project, visitor);
} }
} catch (Exception e) { } catch (Exception e) {
@ -94,7 +94,7 @@ public class ExportRowsCommand extends Command {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
boolean r = false; boolean r = false;
if (total >= start && total < start + limit) { if (total >= start && total < start + limit) {

View File

@ -40,17 +40,28 @@ public class GetRowsCommand extends Command {
RowAccumulator acc = new RowAccumulator(start, limit) { RowAccumulator acc = new RowAccumulator(start, limit) {
JSONWriter writer; JSONWriter writer;
Properties options; Properties options;
Properties extra;
public RowAccumulator init(JSONWriter writer, Properties options) { public RowAccumulator init(JSONWriter writer, Properties options) {
this.writer = writer; this.writer = writer;
this.options = options; this.options = options;
this.extra = new Properties();
this.extra.put("contextual", true);
return this; return this;
} }
@Override @Override
public boolean internalVisit(int rowIndex, Row row) { public boolean internalVisit(int rowIndex, Row row, boolean contextual) {
try { try {
if (contextual) {
options.put("extra", extra);
} else {
options.remove("extra");
}
options.put("rowIndex", rowIndex); options.put("rowIndex", rowIndex);
row.write(writer, options); row.write(writer, options);
} catch (JSONException e) { } catch (JSONException e) {
} }
@ -58,7 +69,7 @@ public class GetRowsCommand extends Command {
} }
}.init(writer, options); }.init(writer, options);
FilteredRows filteredRows = engine.getAllFilteredRows(); FilteredRows filteredRows = engine.getAllFilteredRows(true);
writer.key("rows"); writer.array(); writer.key("rows"); writer.array();
filteredRows.accept(project, acc); filteredRows.accept(project, acc);
@ -89,17 +100,19 @@ public class GetRowsCommand extends Command {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
boolean r = false; boolean r = false;
if (total >= start && total < start + limit) { if (total >= start && total < start + limit) {
r = internalVisit(rowIndex, row); r = internalVisit(rowIndex, row, contextual);
}
if (!contextual) {
total++;
} }
total++;
return r; return r;
} }
protected boolean internalVisit(int rowIndex, Row row) { protected boolean internalVisit(int rowIndex, Row row, boolean contextual) {
return false; return false;
} }
} }

View File

@ -23,7 +23,7 @@ public class ColumnModel implements Serializable, Jsonizable {
final public List<ColumnGroup> columnGroups = new LinkedList<ColumnGroup>(); final public List<ColumnGroup> columnGroups = new LinkedList<ColumnGroup>();
private int _maxCellIndex; private int _maxCellIndex;
private int _keyCellIndex; private int _keyColumnIndex;
transient protected Map<String, Column> _nameToColumn; transient protected Map<String, Column> _nameToColumn;
transient protected Map<Integer, Column> _cellIndexToColumn; transient protected Map<Integer, Column> _cellIndexToColumn;
@ -45,13 +45,13 @@ public class ColumnModel implements Serializable, Jsonizable {
return ++_maxCellIndex; return ++_maxCellIndex;
} }
public void setKeyCellIndex(int keyCellIndex) { public void setKeyColumnIndex(int keyColumnIndex) {
// TODO: check validity of new cell index, e.g., it's not in any group // TODO: check validity of new cell index, e.g., it's not in any group
this._keyCellIndex = keyCellIndex; this._keyColumnIndex = keyColumnIndex;
} }
public int getKeyCellIndex() { public int getKeyColumnIndex() {
return _keyCellIndex; return _keyColumnIndex;
} }
public void update() { public void update() {
@ -79,7 +79,7 @@ public class ColumnModel implements Serializable, Jsonizable {
} }
writer.endArray(); writer.endArray();
writer.key("keyCellIndex"); writer.value(getKeyCellIndex()); writer.key("keyCellIndex"); writer.value(getKeyColumnIndex());
writer.key("columnGroups"); writer.key("columnGroups");
writer.array(); writer.array();
for (ColumnGroup g : _rootColumnGroups) { for (ColumnGroup g : _rootColumnGroups) {

View File

@ -4,8 +4,11 @@ import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import com.metaweb.gridworks.expr.ExpressionUtils;
import com.metaweb.gridworks.history.History; import com.metaweb.gridworks.history.History;
import com.metaweb.gridworks.process.ProcessManager; import com.metaweb.gridworks.process.ProcessManager;
@ -35,10 +38,118 @@ public class Project implements Serializable {
protected void internalInitialize() { protected void internalInitialize() {
processManager = new ProcessManager(); processManager = new ProcessManager();
computeContext(); recomputeRowContextDependencies();
} }
protected void computeContext() { static protected class Group {
// TODO int[] cellIndices;
int keyCellIndex;
}
public void recomputeRowContextDependencies() {
List<Group> keyedGroups = new ArrayList<Group>();
keyedGroups.add(createRootKeyedGroup());
for (ColumnGroup group : columnModel.columnGroups) {
if (group.keyColumnIndex >= 0) {
Group keyedGroup = new Group();
keyedGroup.keyCellIndex = columnModel.columns.get(group.keyColumnIndex).getCellIndex();
keyedGroup.cellIndices = new int[group.columnSpan - 1];
int c = 0;
for (int i = 0; i < group.columnSpan; i++) {
int columnIndex = group.startColumnIndex + i;
if (columnIndex != group.keyColumnIndex) {
int cellIndex = columnModel.columns.get(columnIndex).getCellIndex();
keyedGroup.cellIndices[c++] = cellIndex;
}
}
keyedGroups.add(keyedGroup);
}
}
Collections.sort(keyedGroups, new Comparator<Group>() {
@Override
public int compare(Group o1, Group o2) {
return o2.cellIndices.length - o1.cellIndices.length; // larger groups first
}
});
int[] lastNonBlankRowsByGroup = new int[keyedGroups.size()];
for (int i = 0; i < lastNonBlankRowsByGroup.length; i++) {
lastNonBlankRowsByGroup[i] = -1;
}
for (int r = 0; r < rows.size(); r++) {
Row row = rows.get(r);
row.contextRowSlots = null;
row.contextCellSlots = null;
for (int g = 0; g < keyedGroups.size(); g++) {
Group group = keyedGroups.get(g);
if (ExpressionUtils.isBlank(row.getCellValue(group.keyCellIndex))) {
int contextRowIndex = lastNonBlankRowsByGroup[g];
if (contextRowIndex >= 0) {
for (int dependentCellIndex : group.cellIndices) {
if (!ExpressionUtils.isBlank(row.getCellValue(dependentCellIndex))) {
setRowDependency(
row,
dependentCellIndex,
contextRowIndex,
group.keyCellIndex
);
}
}
}
} else {
lastNonBlankRowsByGroup[g] = r;
}
}
if (row.contextRowSlots != null) {
row.contextRows = new ArrayList<Integer>();
for (int index : row.contextRowSlots) {
if (index >= 0) {
row.contextRows.add(index);
}
}
Collections.sort(row.contextRows);
}
}
}
protected Group createRootKeyedGroup() {
int count = columnModel.getMaxCellIndex();
Group rootKeyedGroup = new Group();
rootKeyedGroup.cellIndices = new int[count - 1];
rootKeyedGroup.keyCellIndex = columnModel.columns.get(columnModel.getKeyColumnIndex()).getCellIndex();
for (int i = 0; i < count; i++) {
if (i < rootKeyedGroup.keyCellIndex) {
rootKeyedGroup.cellIndices[i] = i;
} else if (i > rootKeyedGroup.keyCellIndex) {
rootKeyedGroup.cellIndices[i - 1] = i;
}
}
return rootKeyedGroup;
}
protected void setRowDependency(Row row, int cellIndex, int contextRowIndex, int contextCellIndex) {
int count = columnModel.getMaxCellIndex() + 1;
if (row.contextRowSlots == null || row.contextCellSlots == null) {
row.contextRowSlots = new int[count];
row.contextCellSlots = new int[count];
for (int i = 0; i < count; i++) {
row.contextRowSlots[i] = -1;
row.contextCellSlots[i] = -1;
}
}
row.contextRowSlots[cellIndex] = contextRowIndex;
row.contextCellSlots[cellIndex] = contextCellIndex;
} }
} }

View File

@ -18,8 +18,9 @@ public class Row implements Serializable, HasFields, Jsonizable {
public boolean starred; public boolean starred;
final public List<Cell> cells; final public List<Cell> cells;
transient public List<Integer> contextRows; transient public List<Integer> contextRows;
transient public List<Integer> contextCells; transient public int[] contextRowSlots;
transient public int[] contextCellSlots;
public Row(int cellCount) { public Row(int cellCount) {
cells = new ArrayList<Cell>(cellCount); cells = new ArrayList<Cell>(cellCount);
@ -128,6 +129,15 @@ public class Row implements Serializable, HasFields, Jsonizable {
if (options.containsKey("rowIndex")) { if (options.containsKey("rowIndex")) {
writer.key("i"); writer.value(options.get("rowIndex")); writer.key("i"); writer.value(options.get("rowIndex"));
} }
if (options.containsKey("extra")) {
Properties extra = (Properties) options.get("extra");
if (extra != null) {
for (Object key : extra.keySet()) {
writer.key((String) key);
writer.value(extra.get(key));
}
}
}
writer.endObject(); writer.endObject();
} }

View File

@ -52,7 +52,7 @@ public class ApproveNewReconOperation extends EngineDependentMassCellOperation {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
if (cellIndex < row.cells.size()) { if (cellIndex < row.cells.size()) {
Cell cell = row.cells.get(cellIndex); Cell cell = row.cells.get(cellIndex);

View File

@ -51,7 +51,7 @@ public class ApproveReconOperation extends EngineDependentMassCellOperation {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
if (cellIndex < row.cells.size()) { if (cellIndex < row.cells.size()) {
Cell cell = row.cells.get(cellIndex); Cell cell = row.cells.get(cellIndex);
if (cell.recon != null && cell.recon.candidates.size() > 0) { if (cell.recon != null && cell.recon.candidates.size() > 0) {

View File

@ -63,7 +63,7 @@ public class ColumnAdditionOperation extends EngineDependentOperation {
List<CellAtRow> cellsAtRows = new ArrayList<CellAtRow>(project.rows.size()); List<CellAtRow> cellsAtRows = new ArrayList<CellAtRow>(project.rows.size());
FilteredRows filteredRows = engine.getAllFilteredRows(); FilteredRows filteredRows = engine.getAllFilteredRows(false);
filteredRows.accept(project, createRowVisitor(project, cellsAtRows)); filteredRows.accept(project, createRowVisitor(project, cellsAtRows));
String description = createDescription(column, cellsAtRows); String description = createDescription(column, cellsAtRows);
@ -108,7 +108,7 @@ public class ColumnAdditionOperation extends EngineDependentOperation {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
if (cellIndex < row.cells.size()) { if (cellIndex < row.cells.size()) {
Cell cell = row.cells.get(cellIndex); Cell cell = row.cells.get(cellIndex);
if (cell.value != null) { if (cell.value != null) {

View File

@ -50,7 +50,7 @@ public class DiscardReconOperation extends EngineDependentMassCellOperation {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
if (cellIndex < row.cells.size()) { if (cellIndex < row.cells.size()) {
Cell cell = row.cells.get(cellIndex); Cell cell = row.cells.get(cellIndex);

View File

@ -38,7 +38,7 @@ abstract public class EngineDependentMassCellOperation extends EngineDependentOp
List<CellChange> cellChanges = new ArrayList<CellChange>(project.rows.size()); List<CellChange> cellChanges = new ArrayList<CellChange>(project.rows.size());
FilteredRows filteredRows = engine.getAllFilteredRows(); FilteredRows filteredRows = engine.getAllFilteredRows(false);
filteredRows.accept(project, createRowVisitor(project, cellChanges)); filteredRows.accept(project, createRowVisitor(project, cellChanges));
String description = createDescription(column, cellChanges); String description = createDescription(column, cellChanges);

View File

@ -60,7 +60,7 @@ public class ReconOperation extends EngineDependentOperation {
List<ReconEntry> entries = new ArrayList<ReconEntry>(project.rows.size()); List<ReconEntry> entries = new ArrayList<ReconEntry>(project.rows.size());
FilteredRows filteredRows = engine.getAllFilteredRows(); FilteredRows filteredRows = engine.getAllFilteredRows(false);
filteredRows.accept(project, new RowVisitor() { filteredRows.accept(project, new RowVisitor() {
int cellIndex; int cellIndex;
List<ReconEntry> entries; List<ReconEntry> entries;
@ -72,7 +72,7 @@ public class ReconOperation extends EngineDependentOperation {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
if (cellIndex < row.cells.size()) { if (cellIndex < row.cells.size()) {
Cell cell = row.cells.get(cellIndex); Cell cell = row.cells.get(cellIndex);
if (cell.value != null) { if (cell.value != null) {

View File

@ -62,7 +62,7 @@ public class TextTransformOperation extends EngineDependentMassCellOperation {
} }
@Override @Override
public boolean visit(Project project, int rowIndex, Row row) { public boolean visit(Project project, int rowIndex, Row row, boolean contextual) {
if (cellIndex < row.cells.size()) { if (cellIndex < row.cells.size()) {
Cell cell = row.cells.get(cellIndex); Cell cell = row.cells.get(cellIndex);
if (cell.value != null) { if (cell.value != null) {

View File

@ -250,6 +250,10 @@ DataTableView.prototype.render = function() {
var tr = table.insertRow(table.rows.length); var tr = table.insertRow(table.rows.length);
tr.className = (r % 2) == 1 ? "odd" : "even"; tr.className = (r % 2) == 1 ? "odd" : "even";
if ("contextual" in row && row.contextual) {
$(tr).addClass("contextual");
}
var td = tr.insertCell(tr.cells.length); var td = tr.insertCell(tr.cells.length);
$(td).html((row.i + 1) + "."); $(td).html((row.i + 1) + ".");

View File

@ -14,6 +14,10 @@ table.data-table tr.even {
background: #eee; background: #eee;
} }
table.data-table tr.contextual {
opacity: 0.2;
}
table.data-table td.column-header { table.data-table td.column-header {
background: #ddd; background: #ddd;
cursor: pointer; cursor: pointer;