Made operation "Transpose columns into rows" support the option of transposing into 2 new columns rather than just one.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@2362 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2011-11-06 02:50:33 +00:00
parent 85a37d23f9
commit a35b9f53f7
4 changed files with 234 additions and 84 deletions

View File

@ -53,17 +53,29 @@ public class TransposeColumnsIntoRowsCommand extends Command {
try { try {
Project project = getProject(request); Project project = getProject(request);
AbstractOperation op;
String startColumnName = request.getParameter("startColumnName"); String startColumnName = request.getParameter("startColumnName");
int columnCount = Integer.parseInt(request.getParameter("columnCount")); int columnCount = Integer.parseInt(request.getParameter("columnCount"));
String combinedColumnName = request.getParameter("combinedColumnName");
boolean prependColumnName = Boolean.parseBoolean(request.getParameter("prependColumnName"));
String separator = request.getParameter("separator");
boolean ignoreBlankCells = Boolean.parseBoolean(request.getParameter("ignoreBlankCells")); boolean ignoreBlankCells = Boolean.parseBoolean(request.getParameter("ignoreBlankCells"));
AbstractOperation op = new TransposeColumnsIntoRowsOperation( String combinedColumnName = request.getParameter("combinedColumnName");
startColumnName, columnCount, combinedColumnName, prependColumnName, separator, ignoreBlankCells); if (combinedColumnName != null) {
boolean prependColumnName = Boolean.parseBoolean(request.getParameter("prependColumnName"));
String separator = request.getParameter("separator");
op = new TransposeColumnsIntoRowsOperation(
startColumnName, columnCount,
combinedColumnName, prependColumnName, separator,
ignoreBlankCells);
} else {
String keyColumnName = request.getParameter("keyColumnName");
String valueColumnName = request.getParameter("valueColumnName");
op = new TransposeColumnsIntoRowsOperation(
startColumnName, columnCount,
keyColumnName, valueColumnName,
ignoreBlankCells);
}
Process process = op.createProcess(project, new Properties()); Process process = op.createProcess(project, new Properties());

View File

@ -49,24 +49,40 @@ import com.google.refine.model.Project;
import com.google.refine.model.Row; import com.google.refine.model.Row;
import com.google.refine.model.changes.MassRowColumnChange; import com.google.refine.model.changes.MassRowColumnChange;
import com.google.refine.operations.OperationRegistry; import com.google.refine.operations.OperationRegistry;
import com.google.refine.util.JSONUtilities;
public class TransposeColumnsIntoRowsOperation extends AbstractOperation { public class TransposeColumnsIntoRowsOperation extends AbstractOperation {
final protected String _startColumnName; final protected String _startColumnName;
final protected int _columnCount; final protected int _columnCount;
final protected boolean _ignoreBlankCells;
final protected String _combinedColumnName; final protected String _combinedColumnName;
final protected boolean _prependColumnName; final protected boolean _prependColumnName;
final protected String _separator; final protected String _separator;
final protected boolean _ignoreBlankCells;
final protected String _keyColumnName;
final protected String _valueColumnName;
static public AbstractOperation reconstruct(Project project, JSONObject obj) throws Exception { static public AbstractOperation reconstruct(Project project, JSONObject obj) throws Exception {
String combinedColumnName = JSONUtilities.getString(obj, "combinedColumnName", null);
if (combinedColumnName != null) {
return new TransposeColumnsIntoRowsOperation( return new TransposeColumnsIntoRowsOperation(
obj.getString("startColumnName"), obj.getString("startColumnName"),
obj.getInt("columnCount"), obj.getInt("columnCount"),
obj.getString("combinedColumnName"), combinedColumnName,
obj.getBoolean("prependColumnName"), obj.getBoolean("prependColumnName"),
obj.getString("separator"), obj.getString("separator"),
obj.getBoolean("ignoreBlankCells") obj.getBoolean("ignoreBlankCells")
); );
} else {
return new TransposeColumnsIntoRowsOperation(
obj.getString("startColumnName"),
obj.getInt("columnCount"),
obj.getString("keyColumnName"),
obj.getString("valueColumnName"),
obj.getBoolean("ignoreBlankCells")
);
}
} }
public TransposeColumnsIntoRowsOperation( public TransposeColumnsIntoRowsOperation(
@ -79,10 +95,33 @@ public class TransposeColumnsIntoRowsOperation extends AbstractOperation {
) { ) {
_startColumnName = startColumnName; _startColumnName = startColumnName;
_columnCount = columnCount; _columnCount = columnCount;
_ignoreBlankCells = ignoreBlankCells;
_combinedColumnName = combinedColumnName; _combinedColumnName = combinedColumnName;
_prependColumnName = prependColumnName; _prependColumnName = prependColumnName;
_separator = separator; _separator = separator;
_keyColumnName = null;
_valueColumnName = null;
}
public TransposeColumnsIntoRowsOperation(
String startColumnName,
int columnCount,
String keyColumnName,
String valueColumnName,
boolean ignoreBlankCells
) {
_startColumnName = startColumnName;
_columnCount = columnCount;
_ignoreBlankCells = ignoreBlankCells; _ignoreBlankCells = ignoreBlankCells;
_combinedColumnName = null;
_prependColumnName = false;
_separator = null;
_keyColumnName = keyColumnName;
_valueColumnName = valueColumnName;
} }
@Override @Override
@ -91,37 +130,66 @@ public class TransposeColumnsIntoRowsOperation extends AbstractOperation {
writer.object(); writer.object();
writer.key("op"); writer.value(OperationRegistry.s_opClassToName.get(this.getClass())); writer.key("op"); writer.value(OperationRegistry.s_opClassToName.get(this.getClass()));
writer.key("description"); writer.value( writer.key("description"); writer.value(getBriefDescription());
_columnCount > 0 ?
("Transpose cells in " + _columnCount +
" column(s) starting with " + _startColumnName + " into rows") :
("Transpose cells in columns starting with " +
_startColumnName + " into rows"));
writer.key("startColumnName"); writer.value(_startColumnName); writer.key("startColumnName"); writer.value(_startColumnName);
writer.key("columnCount"); writer.value(_columnCount); writer.key("columnCount"); writer.value(_columnCount);
writer.key("ignoreBlankCells"); writer.value(_ignoreBlankCells);
if (_combinedColumnName != null) {
writer.key("combinedColumnName"); writer.value(_combinedColumnName); writer.key("combinedColumnName"); writer.value(_combinedColumnName);
writer.key("prependColumnName"); writer.value(_prependColumnName); writer.key("prependColumnName"); writer.value(_prependColumnName);
writer.key("separator"); writer.value(_separator); writer.key("separator"); writer.value(_separator);
writer.key("ignoreBlankCells"); writer.value(_ignoreBlankCells); } else {
writer.key("keyColumnName"); writer.value(_keyColumnName);
writer.key("valueColumnName"); writer.value(_valueColumnName);
}
writer.endObject(); writer.endObject();
} }
@Override @Override
protected String getBriefDescription(Project project) { protected String getBriefDescription(Project project) {
return _columnCount > 0 ? return getBriefDescription();
("Transpose cells in " + _columnCount + }
" column(s) starting with " + _startColumnName + " into rows") :
("Transpose cells in columns starting with " + protected String getBriefDescription() {
_startColumnName + " into rows"); if (_combinedColumnName != null) {
if (_columnCount > 0) {
return "Transpose cells in " + _columnCount +
" column(s) starting with " + _startColumnName +
" into rows in one new column named " + _combinedColumnName;
} else {
return "Transpose cells in columns starting with " +
_startColumnName +
" into rows in one new column named " + _combinedColumnName;
}
} else {
if (_columnCount > 0) {
return "Transpose cells in " + _columnCount +
" column(s) starting with " + _startColumnName +
" into rows in two new columns named " +
_keyColumnName + " and " + _valueColumnName;
} else {
return "Transpose cells in columns starting with " +
_startColumnName +
" into rows in two new columns named " +
_keyColumnName + " and " + _valueColumnName;
}
}
} }
@Override @Override
protected HistoryEntry createHistoryEntry(Project project, long historyEntryID) throws Exception { protected HistoryEntry createHistoryEntry(Project project, long historyEntryID) throws Exception {
if (_combinedColumnName != null && if (_combinedColumnName != null) {
!_combinedColumnName.isEmpty() && if (project.columnModel.getColumnByName(_combinedColumnName) != null) {
project.columnModel.getColumnByName(_combinedColumnName) != null) {
throw new Exception("Another column already named " + _combinedColumnName); throw new Exception("Another column already named " + _combinedColumnName);
} }
} else {
if (project.columnModel.getColumnByName(_keyColumnName) != null) {
throw new Exception("Another column already named " + _keyColumnName);
}
if (project.columnModel.getColumnByName(_valueColumnName) != null) {
throw new Exception("Another column already named " + _valueColumnName);
}
}
List<Column> newColumns = new ArrayList<Column>(); List<Column> newColumns = new ArrayList<Column>();
List<Column> oldColumns = project.columnModel.columns; List<Column> oldColumns = project.columnModel.columns;
@ -149,10 +217,12 @@ public class TransposeColumnsIntoRowsOperation extends AbstractOperation {
startColumnIndex = c; startColumnIndex = c;
String columnName = _combinedColumnName != null && _combinedColumnName.length() > 0 ? _combinedColumnName : column.getName(); if (_combinedColumnName != null) {
Column newColumn = new Column(newColumns.size(), columnName); newColumns.add(new Column(newColumns.size(), _combinedColumnName));
} else {
newColumns.add(newColumn); newColumns.add(new Column(newColumns.size(), _keyColumnName));
newColumns.add(new Column(newColumns.size(), _valueColumnName));
}
columnsLeftToTranspose--; columnsLeftToTranspose--;
} else { } else {
@ -172,11 +242,12 @@ public class TransposeColumnsIntoRowsOperation extends AbstractOperation {
startColumnIndex = c; startColumnIndex = c;
String columnName = _combinedColumnName != null && _combinedColumnName.length() > 0 ? if (_combinedColumnName != null) {
_combinedColumnName : column.getName(); newColumns.add(new Column(newColumns.size(), _combinedColumnName));
Column newColumn = new Column(newColumns.size(), columnName); } else {
newColumns.add(new Column(newColumns.size(), _keyColumnName));
newColumns.add(newColumn); newColumns.add(new Column(newColumns.size(), _valueColumnName));
}
break; break;
} else { } else {
// This column is before all columns to transpose // This column is before all columns to transpose
@ -206,8 +277,8 @@ public class TransposeColumnsIntoRowsOperation extends AbstractOperation {
if (c < startColumnIndex) { if (c < startColumnIndex) {
firstNewRow.setCell(c, cell); firstNewRow.setCell(c, cell);
} else if (c == startColumnIndex || c < startColumnIndex + columnCount) { } else if (c == startColumnIndex || c < startColumnIndex + columnCount) {
if (_combinedColumnName != null) {
Cell newCell; Cell newCell;
if (cell == null || cell.value == null) { if (cell == null || cell.value == null) {
if (_prependColumnName && !_ignoreBlankCells) { if (_prependColumnName && !_ignoreBlankCells) {
newCell = new Cell(column.getName() + _separator, null); newCell = new Cell(column.getName() + _separator, null);
@ -220,18 +291,38 @@ public class TransposeColumnsIntoRowsOperation extends AbstractOperation {
newCell = cell; newCell = cell;
} }
Row rowToModify;
if (transposedCells == 0) { if (transposedCells == 0) {
firstNewRow.setCell(startColumnIndex, newCell); rowToModify = firstNewRow;
} else { } else {
Row newRow = new Row(newColumns.size()); rowToModify = new Row(newColumns.size());
newRows.add(rowToModify);
newRow.setCell(startColumnIndex, newCell);
newRows.add(newRow);
} }
rowToModify.setCell(startColumnIndex, newCell);
transposedCells++; transposedCells++;
} else { } else {
firstNewRow.setCell(c - columnCount + 1, cell); if (_ignoreBlankCells && (cell == null || cell.value == null)) {
continue;
}
Row rowToModify;
if (transposedCells == 0) {
rowToModify = firstNewRow;
} else {
rowToModify = new Row(newColumns.size());
newRows.add(rowToModify);
}
rowToModify.setCell(startColumnIndex, new Cell(column.getName(), null));
rowToModify.setCell(startColumnIndex + 1, cell);
transposedCells++;
}
} else {
firstNewRow.setCell(
c - columnCount + (_combinedColumnName != null ? 1 : 2),
cell);
} }
} }
} }
@ -239,7 +330,7 @@ public class TransposeColumnsIntoRowsOperation extends AbstractOperation {
return new HistoryEntry( return new HistoryEntry(
historyEntryID, historyEntryID,
project, project,
getBriefDescription(null), getBriefDescription(),
this, this,
new MassRowColumnChange(newColumns, newRows) new MassRowColumnChange(newColumns, newRows)
); );

View File

@ -253,19 +253,42 @@ DataTableColumnHeaderUI.extendMenu(function(column, columnHeaderUI, menu) {
var config = { var config = {
startColumnName: elmts.fromColumnSelect[0].value, startColumnName: elmts.fromColumnSelect[0].value,
columnCount: elmts.toColumnSelect[0].value, columnCount: elmts.toColumnSelect[0].value,
combinedColumnName: $.trim(elmts.combinedColumnNameInput[0].value),
prependColumnName: elmts.prependColumnNameCheckbox[0].checked,
separator: elmts.separatorInput[0].value,
ignoreBlankCells: elmts.ignoreBlankCellsCheckbox[0].checked ignoreBlankCells: elmts.ignoreBlankCellsCheckbox[0].checked
}; };
var mode = dialog.find('input[name="transpose-dialog-column-choices"]:checked')[0].value;
if (mode == "2") {
config.keyColumnName = $.trim(elmts.keyColumnNameInput[0].value);
config.valueColumnName = $.trim(elmts.valueColumnNameInput[0].value);
if (config.keyColumnName == "") {
alert("Please specify the new key column's name.");
return;
} else if (config.valueColumnName == "") {
alert("Please specify the new value column's name.");
return;
}
} else {
config.combinedColumnName = $.trim(elmts.combinedColumnNameInput[0].value);
config.prependColumnName = elmts.prependColumnNameCheckbox[0].checked;
config.separator = elmts.separatorInput[0].value;
if (config.combinedColumnName == "") {
alert("Please specify the new column's name.");
return;
} else if (config.prependColumnName && config.separator == "") {
alert("Please specify the separator between original column names and cell values.");
return;
}
}
Refine.postCoreProcess( Refine.postCoreProcess(
"transpose-columns-into-rows", "transpose-columns-into-rows",
config, config,
null, null,
{ modelsChanged: true } { modelsChanged: true },
{
onDone: dismiss
}
); );
dismiss();
}); });
for (var i = 0; i < columns.length; i++) { for (var i = 0; i < columns.length; i++) {

View File

@ -1,4 +1,4 @@
<div class="dialog-frame" style="width: 600px;"> <div class="dialog-frame" style="width: 700px;">
<div class="dialog-border"> <div class="dialog-border">
<div class="dialog-header" bind="dialogHeader">Transpose Cells Across Columns into Rows</div> <div class="dialog-header" bind="dialogHeader">Transpose Cells Across Columns into Rows</div>
<div class="dialog-body" bind="dialogBody"> <div class="dialog-body" bind="dialogBody">
@ -6,29 +6,53 @@
<tr> <tr>
<td>From Column</td> <td>From Column</td>
<td>To Column</td> <td>To Column</td>
<td>Formatting Options</td> <td>Transpose into</td>
</tr> </tr>
<tr> <tr>
<td><select bind="fromColumnSelect" size="20" style="width: 100%;"></select></td> <td><select bind="fromColumnSelect" size="20" style="width: 100%;"></select></td>
<td><select bind="toColumnSelect" size="20" style="width: 100%;"></select></td> <td><select bind="toColumnSelect" size="20" style="width: 100%;"></select></td>
<td><div class="grid-layout layout-tight"><table>
<tr>
<td><input type="radio" id="$transpose-dialog-two-columns" name="transpose-dialog-column-choices" value="2" checked /></td>
<td><label for="$transpose-dialog-two-columns">Two new columns</td>
</tr>
<tr>
<td>&nbsp;</td>
<td><div class="grid-layout layout-tightest"><table> <td><div class="grid-layout layout-tightest"><table>
<tr> <tr>
<td colspan="2">Combined column name <input bind="combinedColumnNameInput" size="15" /></td> <td>Key column</td>
<td><input bind="keyColumnNameInput" size="15"> (containing original columns' names)</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><input type="checkbox" bind="prependColumnNameCheckbox" checked id="$transpose-dialog-prepend" /> <td>Value column</td>
<label for="$transpose-dialog-prepend">prepend column name</label></td> <td><input bind="valueColumnNameInput" size="15"> (containing original cells' values)</td>
</tr>
</table></div></td>
</tr>
<tr>
<td><input type="radio" id="$transpose-dialog-one-column" name="transpose-dialog-column-choices" value="1" /></td>
<td><label for="$transpose-dialog-one-column">One column <input bind="combinedColumnNameInput" size="15" /></td>
</tr> </tr>
<tr> <tr>
<td>&nbsp;</td> <td>&nbsp;</td>
<td>Separate column name and cell value with <td><div class="grid-layout layout-tightest"><table>
<input bind="separatorInput" size="5" value=":" /> <tr>
<td><input type="checkbox" bind="prependColumnNameCheckbox" id="$transpose-dialog-prepend" /></td>
<td><label for="$transpose-dialog-prepend">prepend the original column's name to each cell</label></td>
</tr>
</tr>
<tr>
<td>&nbsp;</td>
<td>followed by
<input bind="separatorInput" size="2" value=":" />
before the cell's value
</td> </td>
</tr> </tr>
</table></div></td>
</tr>
<tr> <tr>
<td>&nbsp;</td> <td><input type="checkbox" bind="ignoreBlankCellsCheckbox" checked id="$transpose-dialog-ignore" /></td>
<td><input type="checkbox" bind="ignoreBlankCellsCheckbox" checked id="$transpose-dialog-ignore" /> <td><label for="$transpose-dialog-ignore">Ignore blank cells</label></td>
<label for="$transpose-dialog-ignore">ignore blank cells</label></td>
</tr> </tr>
</table></div></td> </table></div></td>
</tr> </tr>