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 {
final protected List<RowFilter> _rowFilters = new LinkedList<RowFilter>();
final protected boolean _contextual;
public ConjunctiveFilteredRows(boolean contextual) {
_contextual = contextual;
}
public void add(RowFilter rowFilter) {
_rowFilters.add(rowFilter);
@ -16,6 +21,14 @@ public class ConjunctiveFilteredRows implements FilteredRows {
@Override
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++) {
Row row = project.rows.get(i);
@ -28,7 +41,42 @@ public class ConjunctiveFilteredRows implements FilteredRows {
}
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;
}
public FilteredRows getAllFilteredRows() {
return getFilteredRows(null);
public FilteredRows getAllFilteredRows(boolean contextual) {
return getFilteredRows(null, contextual);
}
public FilteredRows getFilteredRows(Facet except) {
ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows();
public FilteredRows getFilteredRows(Facet except, boolean contextual) {
ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows(contextual);
for (Facet facet : _facets) {
if (facet != except) {
RowFilter rowFilter = facet.getRowFilter();
@ -68,7 +68,7 @@ public class Engine implements Jsonizable {
public void computeFacets() throws JSONException {
for (Facet facet : _facets) {
FilteredRows filteredRows = getFilteredRows(facet);
FilteredRows filteredRows = getFilteredRows(facet, false);
facet.computeChoices(_project, filteredRows);
}

View File

@ -4,6 +4,6 @@ import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row;
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
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()) {
Cell cell = row.cells.get(_cellIndex);
if (cell != null) {

View File

@ -24,7 +24,7 @@ public class ExpressionNumericRowBinner implements RowVisitor {
}
@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()) {
Cell cell = row.cells.get(_cellIndex);
if (cell != null) {

View File

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

View File

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

View File

@ -23,7 +23,7 @@ public class ColumnModel implements Serializable, Jsonizable {
final public List<ColumnGroup> columnGroups = new LinkedList<ColumnGroup>();
private int _maxCellIndex;
private int _keyCellIndex;
private int _keyColumnIndex;
transient protected Map<String, Column> _nameToColumn;
transient protected Map<Integer, Column> _cellIndexToColumn;
@ -45,13 +45,13 @@ public class ColumnModel implements Serializable, Jsonizable {
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
this._keyCellIndex = keyCellIndex;
this._keyColumnIndex = keyColumnIndex;
}
public int getKeyCellIndex() {
return _keyCellIndex;
public int getKeyColumnIndex() {
return _keyColumnIndex;
}
public void update() {
@ -79,7 +79,7 @@ public class ColumnModel implements Serializable, Jsonizable {
}
writer.endArray();
writer.key("keyCellIndex"); writer.value(getKeyCellIndex());
writer.key("keyCellIndex"); writer.value(getKeyColumnIndex());
writer.key("columnGroups");
writer.array();
for (ColumnGroup g : _rootColumnGroups) {

View File

@ -4,8 +4,11 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.metaweb.gridworks.expr.ExpressionUtils;
import com.metaweb.gridworks.history.History;
import com.metaweb.gridworks.process.ProcessManager;
@ -35,10 +38,118 @@ public class Project implements Serializable {
protected void internalInitialize() {
processManager = new ProcessManager();
computeContext();
recomputeRowContextDependencies();
}
protected void computeContext() {
// TODO
static protected class Group {
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;
final public List<Cell> cells;
transient public List<Integer> contextRows;
transient public List<Integer> contextCells;
transient public List<Integer> contextRows;
transient public int[] contextRowSlots;
transient public int[] contextCellSlots;
public Row(int cellCount) {
cells = new ArrayList<Cell>(cellCount);
@ -128,6 +129,15 @@ public class Row implements Serializable, HasFields, Jsonizable {
if (options.containsKey("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();
}

View File

@ -52,7 +52,7 @@ public class ApproveNewReconOperation extends EngineDependentMassCellOperation {
}
@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()) {
Cell cell = row.cells.get(cellIndex);

View File

@ -51,7 +51,7 @@ public class ApproveReconOperation extends EngineDependentMassCellOperation {
}
@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()) {
Cell cell = row.cells.get(cellIndex);
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());
FilteredRows filteredRows = engine.getAllFilteredRows();
FilteredRows filteredRows = engine.getAllFilteredRows(false);
filteredRows.accept(project, createRowVisitor(project, cellsAtRows));
String description = createDescription(column, cellsAtRows);
@ -108,7 +108,7 @@ public class ColumnAdditionOperation extends EngineDependentOperation {
}
@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()) {
Cell cell = row.cells.get(cellIndex);
if (cell.value != null) {

View File

@ -50,7 +50,7 @@ public class DiscardReconOperation extends EngineDependentMassCellOperation {
}
@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()) {
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());
FilteredRows filteredRows = engine.getAllFilteredRows();
FilteredRows filteredRows = engine.getAllFilteredRows(false);
filteredRows.accept(project, createRowVisitor(project, 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());
FilteredRows filteredRows = engine.getAllFilteredRows();
FilteredRows filteredRows = engine.getAllFilteredRows(false);
filteredRows.accept(project, new RowVisitor() {
int cellIndex;
List<ReconEntry> entries;
@ -72,7 +72,7 @@ public class ReconOperation extends EngineDependentOperation {
}
@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()) {
Cell cell = row.cells.get(cellIndex);
if (cell.value != null) {

View File

@ -62,7 +62,7 @@ public class TextTransformOperation extends EngineDependentMassCellOperation {
}
@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()) {
Cell cell = row.cells.get(cellIndex);
if (cell.value != null) {

View File

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

View File

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