mirror of
https://github.com/enso-org/enso.git
synced 2024-11-23 08:08:34 +03:00
Case Insensitive Dataframe Support in Visualizations (#1634)
Ref https://github.com/enso-org/ide/issues/1391
This commit is contained in:
parent
547db918e5
commit
8d77a565eb
35
distribution/std-lib/Standard/src/Visualization/Geo_Map.enso
Normal file
35
distribution/std-lib/Standard/src/Visualization/Geo_Map.enso
Normal file
@ -0,0 +1,35 @@
|
||||
from Standard.Base import all
|
||||
|
||||
import Standard.Table.Data.Table
|
||||
import Standard.Test
|
||||
import Standard.Visualization.Helpers
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Construct JSON describing table geo map visualization.
|
||||
|
||||
Arguments:
|
||||
- table: the Table to be visualized.
|
||||
json_from_table : Table.Table -> Object
|
||||
json_from_table table =
|
||||
names = ['label', 'latitude', 'longitude', 'radius', 'color']
|
||||
pairs = names.filter_map <| name->
|
||||
column = table.lookup_ignore_case name
|
||||
column.when_valid ["df_" + name, column.to_vector]
|
||||
|
||||
Json.from_pairs pairs
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Default preprocessor for the geo map visualization, generating JSON text
|
||||
describing the geo map visualization.
|
||||
|
||||
Arguments:
|
||||
- value: the value to be visualized.
|
||||
process_to_json_text : Any -> Text
|
||||
process_to_json_text value =
|
||||
json = case value of
|
||||
Table.Table _ -> here.json_from_table value
|
||||
_ -> value.to_json
|
||||
|
||||
json.to_text
|
@ -5,11 +5,85 @@ import Standard.Table.Data.Storage
|
||||
import Standard.Table.Data.Table
|
||||
|
||||
## PRIVATE
|
||||
Any.catch_: Any -> Any
|
||||
|
||||
Maps the vector using the given function. Filters out all error values.
|
||||
|
||||
Arguments:
|
||||
- f: unary invokable that is applied to each vector element. Non-error
|
||||
values are returned in the resulting vector. Error values are dropped.
|
||||
Vector.Vector.filter_map : Any -> Vector
|
||||
Vector.Vector.filter_map f = this.map f . filter .is_valid
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Returns the given value if this is not an error. Propagates error otherwise.
|
||||
|
||||
Arguments:
|
||||
- val: a value that will be evaluated and returned if `this` is an error.
|
||||
Any.when_valid : Any -> Any
|
||||
Any.when_valid ~val = this.map_valid (_-> val)
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Returns the given value if this is not an error. Propagates error otherwise.
|
||||
|
||||
Arguments:
|
||||
- val: a value that will be evaluated and returned if `this` is an error.
|
||||
Error.when_valid : Any -> Any
|
||||
Error.when_valid ~val = this.map_valid (_-> val)
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Checks if the value is not an error.
|
||||
Any.is_valid : Any
|
||||
Any.is_valid = this.is_error.not
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Checks if the value is not an error.
|
||||
Error.is_valid : Any
|
||||
Error.is_valid = this.is_error.not
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Maps over non-error value.
|
||||
|
||||
Arguments:
|
||||
- f: a function that will be used to generate return value from a non-error
|
||||
`this` value.
|
||||
Any.map_valid : Any -> Any
|
||||
Any.map_valid f = f this
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Maps over non-error value.
|
||||
|
||||
Arguments:
|
||||
- _: a function that will be used to generate return value from a non-error
|
||||
`this` value.
|
||||
Error.map_valid : Any -> Any
|
||||
Error.map_valid _ = this
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Recovers from the error by returning the parameter value.
|
||||
|
||||
The error contents will be ignored.
|
||||
|
||||
Arguments:
|
||||
- val: a value that will be evaluated and returned if `this` is an error.
|
||||
Any.catch_ : Any -> Any
|
||||
Any.catch_ ~val = this.catch (_-> val)
|
||||
|
||||
## PRIVATE
|
||||
Any.catch_ : Any -> Any
|
||||
|
||||
Recovers from the error by returning the parameter value.
|
||||
|
||||
The error contents will be ignored.
|
||||
|
||||
Arguments:
|
||||
- val: a value that will be evaluated and returned if `this` is an error.
|
||||
Error.catch_ : Any -> Any
|
||||
Error.catch_ ~val = this.catch (_-> val)
|
||||
|
||||
## PRIVATE
|
||||
@ -21,7 +95,8 @@ recover_errors ~body =
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Returns all the columns in the table, including indices.
|
||||
Returns all the columns in the table, including indices.
|
||||
|
||||
Index columns are placed before other columns.
|
||||
Table.Table.all_columns : Vector
|
||||
Table.Table.all_columns =
|
||||
@ -31,6 +106,19 @@ Table.Table.all_columns =
|
||||
a -> [a]
|
||||
index_columns + this.columns
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Looks for a column by a given name.
|
||||
|
||||
Unlike `Table.at` looks into index columns and name comparison is case-insensitive.
|
||||
|
||||
Arguments:
|
||||
- text: the case-insensitive name of the searched column.
|
||||
Table.Table.lookup_ignore_case : Text -> Column ! Nothing
|
||||
Table.Table.lookup_ignore_case name =
|
||||
ret = this.all_columns.find <| col->
|
||||
col.name.equals_ignore_case name
|
||||
ret
|
||||
|
||||
## PRIVATE
|
||||
|
||||
|
@ -13,8 +13,8 @@ Table.Table.first_numeric = this.all_columns.find _.is_numeric
|
||||
Get the value column - the column that will be used to create histogram.
|
||||
Table.Table.value_column : Table -> Column ! Nothing
|
||||
Table.Table.value_column =
|
||||
named_col = this.at 'value'
|
||||
named_col.catch_ <| this.first_numeric
|
||||
named_col = this.lookup_ignore_case 'value'
|
||||
named_col.catch_ this.first_numeric
|
||||
|
||||
## PRIVATE
|
||||
Information that are placed in an update sent to a visualization.
|
||||
|
@ -81,7 +81,7 @@ type PointData
|
||||
## PRIVATE
|
||||
lookup_in : Table -> Column
|
||||
lookup_in table =
|
||||
named = table.at this.name
|
||||
named = table.lookup_ignore_case this.name
|
||||
named.catch_ <| this.fallback_column table
|
||||
|
||||
## PRIVATE
|
||||
@ -89,8 +89,7 @@ type PointData
|
||||
Table.Table.point_data : Table -> Object
|
||||
Table.Table.point_data =
|
||||
get_point_data field = field.lookup_in this . rename field.name
|
||||
is_valid column = column.is_error.not
|
||||
columns = PointData.all_fields.map get_point_data . filter is_valid
|
||||
columns = PointData.all_fields.filter_map get_point_data
|
||||
(0.up_to <| this.row_count + 1).to_vector.map <| row_n->
|
||||
pairs = columns.map column->
|
||||
value = column.at row_n . catch_ Nothing
|
||||
@ -109,7 +108,7 @@ Table.Table.axes =
|
||||
y_axis = describe_axis Y
|
||||
is_valid axis_pair =
|
||||
label = axis_pair.at 1
|
||||
label.is_error.not && (this.all_columns.length > 0)
|
||||
label.is_valid && (this.all_columns.length > 0)
|
||||
axes_obj = Json.from_pairs <| [x_axis, y_axis].filter is_valid
|
||||
if axes_obj.fields.size > 0 then axes_obj else Nothing
|
||||
|
||||
|
36
test/Visualization_Tests/src/Geo_Map_Spec.enso
Normal file
36
test/Visualization_Tests/src/Geo_Map_Spec.enso
Normal file
@ -0,0 +1,36 @@
|
||||
from Standard.Base import all
|
||||
|
||||
import Standard.Table.Data.Table
|
||||
import Standard.Test
|
||||
import Standard.Visualization.Geo_Map
|
||||
import Visualization_Tests.Helpers
|
||||
|
||||
spec =
|
||||
expect value expected_json_text =
|
||||
result = Geo_Map.process_to_json_text value
|
||||
Json.parse result . should_equal <| Json.parse expected_json_text
|
||||
|
||||
Test.group "Geo_Map" <|
|
||||
Test.specify "works with empty table" <|
|
||||
table = Table.from_rows [] []
|
||||
expect table '{}'
|
||||
|
||||
Test.specify "skips unrecognized columns" <|
|
||||
header = ['α' , 'β' , 'ω']
|
||||
row_1 = [11 , 10 , 09 ]
|
||||
row_2 = [21 , 20 , 19 ]
|
||||
table = Table.from_rows header [row_1, row_2]
|
||||
expect table '{}'
|
||||
|
||||
Test.specify "recognizes relevant columns" <|
|
||||
header = ['latitude' , 'longitude' , 'color' , 'label' , 'radius']
|
||||
row_1 = [11 , 10 , 'red' , 'name' , 195 ]
|
||||
table = Table.from_rows header [row_1]
|
||||
expect table '{"df_color":["red"],"df_label":["name"],"df_latitude":[11],"df_longitude":[10],"df_radius":[195]}'
|
||||
|
||||
Test.specify "is case insensitive" <|
|
||||
header = ['latitude' , 'LONGITUDE' , 'LaBeL']
|
||||
row_1 = [11 , 10 , 09 ]
|
||||
row_2 = [21 , 20 , 19 ]
|
||||
table = Table.from_rows header [row_1, row_2]
|
||||
expect table '{"df_label":[9,19],"df_latitude":[11,21],"df_longitude":[10,20]}'
|
9
test/Visualization_Tests/src/Helpers.enso
Normal file
9
test/Visualization_Tests/src/Helpers.enso
Normal file
@ -0,0 +1,9 @@
|
||||
from Standard.Base import all
|
||||
|
||||
import Standard.Table.Data.Column
|
||||
import Standard.Test
|
||||
|
||||
Column.Column.expect : Text -> Vector -> Test.Success
|
||||
Column.Column.expect name contents =
|
||||
this.name.should_equal name
|
||||
this.to_vector.should_equal contents
|
@ -3,8 +3,7 @@ from Standard.Base import all
|
||||
import Standard.Table.Data.Table
|
||||
import Standard.Test
|
||||
import Standard.Visualization.Helpers
|
||||
|
||||
import Visualization_Tests
|
||||
import Visualization_Tests.Helpers
|
||||
|
||||
spec =
|
||||
Test.group "Table.all_columns" <|
|
||||
@ -26,9 +25,22 @@ spec =
|
||||
table = Table.from_rows header [row_1, row_2]
|
||||
table.all_columns.map (_.name) . should_equal ['a']
|
||||
|
||||
Test.specify "includes both normal and index columns" <|
|
||||
Test.specify "includes the index first and then normal columns" <|
|
||||
header = ['a', 'b']
|
||||
row_1 = [11 , 10 ]
|
||||
row_2 = [21 , 20 ]
|
||||
table = Table.from_rows header [row_1, row_2] . set_index 'a'
|
||||
table.all_columns.map (_.name) . should_equal ['a','b']
|
||||
|
||||
Test.group "Table.lookup_ignore_case" <|
|
||||
Test.specify "ignores case and takes first matching" <|
|
||||
header = ['A', 'a' , 'ω' , 'Ω']
|
||||
row_1 = [11 , 10 , 12 , 13]
|
||||
row_2 = [21 , 20 , 22 , 23]
|
||||
table = Table.from_rows header [row_1, row_2]
|
||||
table.lookup_ignore_case 'a' . expect 'A' [11,21]
|
||||
table.lookup_ignore_case 'A' . expect 'A' [11,21]
|
||||
table.lookup_ignore_case 'ω' . expect 'ω' [12,22]
|
||||
table.lookup_ignore_case 'Ω' . expect 'ω' [12,22]
|
||||
table.lookup_ignore_case 'b' . is_error . should_equal True
|
||||
table.lookup_ignore_case 'B' . is_error . should_equal True
|
||||
|
@ -4,7 +4,6 @@ import Standard.Table.Data.Column
|
||||
import Standard.Table.Data.Table
|
||||
import Standard.Test
|
||||
import Standard.Visualization.Histogram
|
||||
|
||||
import Visualization_Tests
|
||||
|
||||
spec =
|
||||
@ -47,6 +46,13 @@ spec =
|
||||
table = Table.from_rows header [row_1, row_2]
|
||||
expect table 'value' [10,20]
|
||||
|
||||
Test.specify "is case insensitive" <|
|
||||
header = ['α', 'Value']
|
||||
row_1 = [11 , 10 ]
|
||||
row_2 = [21 , 20 ]
|
||||
table = Table.from_rows header [row_1, row_2]
|
||||
expect table 'Value' [10,20]
|
||||
|
||||
Test.specify "plots column" <|
|
||||
column = Column.from_vector 'my_name' [1,4,6]
|
||||
expect column 'my_name' [1,4,6]
|
||||
|
@ -2,6 +2,7 @@ from Standard.Base import all
|
||||
|
||||
import Standard.Test
|
||||
|
||||
import Visualization_Tests.Geo_Map_Spec
|
||||
import Visualization_Tests.Helpers_Spec
|
||||
import Visualization_Tests.Histogram_Spec
|
||||
import Visualization_Tests.Scatter_Plot_Spec
|
||||
@ -9,6 +10,7 @@ import Visualization_Tests.Sql_Spec
|
||||
import Visualization_Tests.Table_Spec
|
||||
|
||||
main = Test.Suite.runMain <|
|
||||
Geo_Map_Spec.spec
|
||||
Helpers_Spec.spec
|
||||
Histogram_Spec.spec
|
||||
Scatter_Plot_Spec.spec
|
||||
|
@ -49,6 +49,12 @@ spec =
|
||||
table = Table.from_rows header [row_1]
|
||||
expect table (labels 'x' 'y') '[{"color":"ff0000","label":"label","shape":"square","size":50,"x":11,"y":10}]'
|
||||
|
||||
Test.specify "is case insensitive" <|
|
||||
header = ['X' , 'Y' , 'Size' , 'Shape' , 'Label' , 'Color' ]
|
||||
row_1 = [11 , 10 , 50 , 'square' , 'label' , 'ff0000']
|
||||
table = Table.from_rows header [row_1]
|
||||
expect table (labels 'X' 'Y') '[{"color":"ff0000","label":"label","shape":"square","size":50,"x":11,"y":10}]'
|
||||
|
||||
Test.specify "uses first unrecognized numeric column as `y` fallback" <|
|
||||
header = ['x' , 'size' , 'name' , 'z' , 'ω']
|
||||
row_1 = [11 , 50 , 'circul' , 20 , 30]
|
||||
@ -69,16 +75,13 @@ spec =
|
||||
table = Table.from_rows header [row_1, row_2] . set_index 'baz'
|
||||
# [TODO] mwu: When it is possible to set multiple index columns, test such case.
|
||||
expect table (labels 'baz' 'y') '[{"size":40,"x":14,"y":10},{"size":50,"x":15,"y":20}]'
|
||||
|
||||
|
||||
Test.specify "prefers explicit 'x' to index and looks into indices for recognized fields" <|
|
||||
header = [ 'x' , 'size']
|
||||
row_1 = [ 10 , 21 ]
|
||||
row_2 = [ 20 , 22 ]
|
||||
table = Table.from_rows header [row_1, row_2] . set_index 'size'
|
||||
# FIXME [mwu] Below the `size` field should be present. Depends on
|
||||
# https://github.com/enso-org/enso/issues/1602
|
||||
expect table (labels 'x' 'size') '[{"x":10,"y":21},{"x":20,"y":22}]'
|
||||
expect table (labels 'x' 'size') '[{"size":21,"x":10,"y":21},{"size":22,"x":20,"y":22}]'
|
||||
|
||||
Test.specify "used default index for `x` if none set" <|
|
||||
header = [ 'y' , 'bar' , 'size']
|
||||
|
Loading…
Reference in New Issue
Block a user