Skip to content

Commit

Permalink
apacheGH-38166: [MATLAB] Improve tabular object display (apache#38482)
Browse files Browse the repository at this point in the history
### Rationale for this change

Currently, the display for `arrow.tabular.RecordBatch` and `arrow.tabular.Table` are not very MATLAB-like. 

### What changes are included in this PR?

1. Updated the display of both `arrow.tabular.Table` and `arrow.tabular.RecordBatch`. 
2. Added a new utility function `arrow.internal.display.boldFontIfPossible`
3. Renamed  `arrow.array.internal.display.pluralizeStringIfNeeded` to `arrow.internal.display.pluralizeStringIfNeeded`
4. Renamed `arrow.tabular.internal.displaySchema` to `arrow.tabular.internal.display.getSchemaString`.

**Example RecordBatch Display**

```matlab
>> t = table(1, false, datetime(2023, 1, 1), VariableNames=["Number", "Logical", "Date"]);
>> rb = arrow.recordBatch(t)

rb = 

  Arrow RecordBatch with 1 row and 3 columns:

    Schema:

        Number: Float64 | Logical: Boolean | Date: Timestamp

    First Row:

        1 | false | 2023-01-01 00:00:00.000000
```

**Example Table Display**

```matlab
>> t = table(1, false, datetime(2023, 1, 1), VariableNames=["Number", "Logical", "Date"]);
>> arrowTable = arrow.table(t)

arrowTable = 

  Arrow Table with 1 row and 3 columns:

    Schema:

        Number: Float64 | Logical: Boolean | Date: Timestamp

    First Row:

        1 | false | 2023-01-01 00:00:00.000000
```

### Are these changes tested?

Yes, I added a new test class in `matlab/test/arrow/tabular` called `tTabularDisplay.m`.

### Are there any user-facing changes?

Yes. Users will now see the new `Table`/`RecordBatch` display 

* Closes: apache#38166

Authored-by: Sarah Gilmore <[email protected]>
Signed-off-by: Kevin Gurney <[email protected]>
  • Loading branch information
sgilmore10 authored Oct 27, 2023
1 parent 6e6fd0a commit 547b240
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
% permissions and limitations under the License.

function header = getHeader(className, numElements, numNulls)
import arrow.array.internal.display.pluralizeStringIfNeeded
import arrow.internal.display.pluralizeStringIfNeeded
elementString = pluralizeStringIfNeeded(numElements, "element");

nullString = pluralizeStringIfNeeded(numNulls, "null value");
Expand Down
26 changes: 26 additions & 0 deletions matlab/src/matlab/+arrow/+internal/+display/boldFontIfPossible.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
%BOLDFONTIFPOSSIBLE Bolds the input string if possible

% Licensed to the Apache Software Foundation (ASF) under one or more
% contributor license agreements. See the NOTICE file distributed with
% this work for additional information regarding copyright ownership.
% The ASF licenses this file to you under the Apache License, Version
% 2.0 (the "License"); you may not use this file except in compliance
% with the License. You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
% implied. See the License for the specific language governing
% permissions and limitations under the License.

function str = boldFontIfPossible(str)

arguments
str(1, 1) string {mustBeNonzeroLengthText}
end
if usejava("desktop")
str = compose("<strong>%s</strong>", str);
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
%DISPLAYSCHEMA Generates arrow.tabular.Schema display text.
%GETSCHEMASTRING Generates a string representation of an
% arrow.tabular.Schema object.

% Licensed to the Apache Software Foundation (ASF) under one or more
% contributor license agreements. See the NOTICE file distributed with
Expand All @@ -15,7 +16,7 @@
% implied. See the License for the specific language governing
% permissions and limitations under the License.

function text = displaySchema(schema)
function text = getSchemaString(schema)
fields = schema.Fields;
names = [fields.Name];
types = [fields.Type];
Expand Down Expand Up @@ -46,5 +47,5 @@
end

text = names + ": " + typeIDs;
text = " " + strjoin(text, " | ");
text = strjoin(text, " | ");
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
%GETTABULARDISPLAY Generates the display for arrow.tabular.Table and
% arrow.tabular.RecordBatch.

% Licensed to the Apache Software Foundation (ASF) under one or more
% contributor license agreements. See the NOTICE file distributed with
% this work for additional information regarding copyright ownership.
% The ASF licenses this file to you under the Apache License, Version
% 2.0 (the "License"); you may not use this file except in compliance
% with the License. You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
% implied. See the License for the specific language governing
% permissions and limitations under the License.

function tabularDisplay = getTabularDisplay(tabularObj, className)
import arrow.tabular.internal.display.getSchemaString
import arrow.tabular.internal.display.getTabularHeader

numRows = tabularObj.NumRows;
numColumns = tabularObj.NumColumns;
tabularDisplay = getTabularHeader(className, numRows, numColumns);

if numColumns > 0
twoNewLines = string([newline newline]);
fourSpaces = string(repmat(' ', 1, 4));
eightSpaces = string(repmat(' ', 1, 8));

schemaHeader = fourSpaces + "Schema:";
schemaBody = eightSpaces + getSchemaString(tabularObj.Schema);
schemaDisplay = schemaHeader + twoNewLines + schemaBody;
tabularDisplay = tabularDisplay + twoNewLines + schemaDisplay;

if numRows > 0
rowHeader = fourSpaces + "First Row:";
rowBody = eightSpaces + tabularObj.Proxy.getRowAsString(struct(Index=int64(1)));
rowDisplay = rowHeader + twoNewLines + rowBody;
tabularDisplay = tabularDisplay + twoNewLines + rowDisplay;
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
%GETTABULARHEADER Generates the display header for arrow.tabular.Table and
% arrow.tabular.RecordBatch.

% Licensed to the Apache Software Foundation (ASF) under one or more
% contributor license agreements. See the NOTICE file distributed with
% this work for additional information regarding copyright ownership.
% The ASF licenses this file to you under the Apache License, Version
% 2.0 (the "License"); you may not use this file except in compliance
% with the License. You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
% implied. See the License for the specific language governing
% permissions and limitations under the License.

function header = getTabularHeader(className, numRows, numColumns)
import arrow.internal.display.boldFontIfPossible
import arrow.internal.display.pluralizeStringIfNeeded

numRowsString = boldFontIfPossible(numRows);
numColsString = boldFontIfPossible(numColumns);
rowWordString = pluralizeStringIfNeeded(numRows, "row");
colWordString = pluralizeStringIfNeeded(numColumns, "column");
formatSpec = " Arrow %s with %s %s and %s %s";
if numColumns > 0
formatSpec = formatSpec + ":";
end
header = compose(formatSpec,className, numRowsString, rowWordString, numColsString, colWordString);
end
4 changes: 3 additions & 1 deletion matlab/src/matlab/+arrow/+tabular/RecordBatch.m
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@

methods (Access=protected)
function displayScalarObject(obj)
disp(obj.toString());
className = matlab.mixin.CustomDisplay.getClassNameForHeader(obj);
tabularDisplay = arrow.tabular.internal.display.getTabularDisplay(obj, className);
disp(tabularDisplay + newline);
end
end

Expand Down
2 changes: 1 addition & 1 deletion matlab/src/matlab/+arrow/+tabular/Schema.m
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function displayScalarObject(obj)
numFields = obj.NumFields;

if numFields > 0
text = arrow.tabular.internal.displaySchema(obj);
text = " " + arrow.tabular.internal.display.getSchemaString(obj);
disp(text + newline);
end

Expand Down
5 changes: 3 additions & 2 deletions matlab/src/matlab/+arrow/+tabular/Table.m
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,10 @@
end

methods (Access=protected)

function displayScalarObject(obj)
disp(obj.toString());
className = matlab.mixin.CustomDisplay.getClassNameForHeader(obj);
tabularDisplay = arrow.tabular.internal.display.getTabularDisplay(obj, className);
disp(tabularDisplay + newline);
end

end
Expand Down
148 changes: 148 additions & 0 deletions matlab/test/arrow/tabular/tTabularDisplay.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
%TTABULARDISPLAY Unit tests verifying the display of arrow.tabular.Table
%and arrow.tabular.RecordBatch objects.

% Licensed to the Apache Software Foundation (ASF) under one or more
% contributor license agreements. See the NOTICE file distributed with
% this work for additional information regarding copyright ownership.
% The ASF licenses this file to you under the Apache License, Version
% 2.0 (the "License"); you may not use this file except in compliance
% with the License. You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
% implied. See the License for the specific language governing
% permissions and limitations under the License.

classdef tTabularDisplay < matlab.unittest.TestCase

properties (TestParameter)
TabularType
end

methods (TestParameterDefinition, Static)
function TabularType = initializeTabularType()

tableStruct = struct(FullClassName="arrow.tabular.Table", ...
ClassName="Table", FromTableFcn = @arrow.table);

recordBatchStruct = struct(FullClassName="arrow.tabular.RecordBatch", ...
ClassName="RecordBatch", FromTableFcn=@arrow.recordBatch);

TabularType = struct(Table=tableStruct, RecordBatch=recordBatchStruct);
end

end

methods (Test)

function ZeroRowsZeroColumns(testCase, TabularType)
% Verify tabular object display when the object has zero rows
% and zero columns.
import arrow.internal.test.display.makeLinkString

tabularObj = TabularType.FromTableFcn(table); %#ok<NASGU>

classNameString = makeLinkString(FullClassName=TabularType.FullClassName, ...
ClassName=TabularType.ClassName, BoldFont=true);
zeroString = getNumString(0);
header = compose(" Arrow %s with %s rows and %s columns", classNameString, zeroString, zeroString);
expectedDisplay = char(header + newline + newline);
actualDisplay = evalc('disp(tabularObj)');

testCase.verifyEqual(actualDisplay, expectedDisplay);
end

function ZeroRowsOneColumn(testCase, TabularType)
% Verify tabular object display when the object has zero rows
% and one column.
import arrow.internal.test.display.makeLinkString

t = table(1, VariableNames="Number");
tabularObj = TabularType.FromTableFcn(t(1:0, :)); %#ok<NASGU>

classNameString = makeLinkString(FullClassName=TabularType.FullClassName, ...
ClassName=TabularType.ClassName, BoldFont=true);
header = compose(" Arrow %s with %s rows and %s column:", classNameString, getNumString(0), getNumString(1));

fieldString = makeFieldString("Number", "Float64", "arrow.type.Float64Type");
schema = join([" Schema:" " " + fieldString], [newline newline]);

expectedDisplay = char(join([header schema + newline + newline], [newline newline]));
actualDisplay = evalc('disp(tabularObj)');

testCase.verifyEqual(actualDisplay, expectedDisplay);
end

function OneRowOneColumn(testCase, TabularType)
% Verify tabular object display when the object has one row
% and column.
import arrow.internal.test.display.makeLinkString

t = table(1, VariableNames="Number");
tabularObj = TabularType.FromTableFcn(t); %#ok<NASGU>

classNameString = makeLinkString(FullClassName=TabularType.FullClassName, ...
ClassName=TabularType.ClassName, BoldFont=true);
header = compose(" Arrow %s with %s row and %s column:", classNameString, getNumString(1), getNumString(1));

fieldString = makeFieldString("Number", "Float64", "arrow.type.Float64Type");
schema = join([" Schema:" " " + fieldString], [newline newline]);
row = join([" First Row:" " 1"], [newline newline]);


expectedDisplay = char(join([header schema row + newline + newline], [newline newline]));
actualDisplay = evalc('disp(tabularObj)');

testCase.verifyEqual(actualDisplay, expectedDisplay);
end

function ManyRowsAndColumns(testCase, TabularType)
% Verify tabular object display when the object has many rows
% and columns.
import arrow.internal.test.display.makeLinkString

t = table((1:2)', ["A"; "B"], true(2, 1), VariableNames=["Number", "Letter", "Logical"]);
tabularObj = TabularType.FromTableFcn(t); %#ok<NASGU>

classNameString = makeLinkString(FullClassName=TabularType.FullClassName, ...
ClassName=TabularType.ClassName, BoldFont=true);
header = compose(" Arrow %s with %s rows and %s columns:", classNameString, getNumString(2), getNumString(3));

fieldOneString = makeFieldString("Number", "Float64", "arrow.type.Float64Type");
fieldTwoString = makeFieldString("Letter", "String", "arrow.type.StringType");
fieldThreeString = makeFieldString("Logical", "Boolean", "arrow.type.BooleanType");

fields = join([fieldOneString fieldTwoString fieldThreeString], " | ");
schema = join([" Schema:" " " + fields], [newline newline]);
row = join([" First Row:" " 1 | ""A"" | true"], [newline newline]);

expectedDisplay = char(join([header schema row + newline + newline], [newline newline]));
actualDisplay = evalc('disp(tabularObj)');

testCase.verifyEqual(actualDisplay, expectedDisplay);
end
end
end

function numString = getNumString(num)
if usejava("desktop")
numString = compose("<strong>%d</strong>", num);
else
numString = compose("%d", num);
end
end

function str = makeFieldString(fieldName, classType, fullClassType)
import arrow.internal.test.display.makeLinkString

if usejava("desktop")
name = compose("<strong>%s</strong>:", fieldName);
typeStr = makeLinkString(FullClassName=fullClassType, ClassName=classType, BoldFont=true);
str = name + " " + typeStr;
else
str = fieldName + ": " + classType;
end
end

0 comments on commit 547b240

Please sign in to comment.