Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Histogram and Scatterplot visualizations support for Table #1608

Merged
merged 5 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions distribution/std-lib/Standard/src/Visualization/Helpers.enso
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import Standard.Table.Data.Storage
import Standard.Table.Data.Table

## PRIVATE
Any.catch_: Any -> Any
Any.catch_ ~val = this.catch (_-> val)
iamrecursion marked this conversation as resolved.
Show resolved Hide resolved

## PRIVATE
Any.catch_ : Any -> Any
Error.catch_ ~val = this.catch (_-> val)

## PRIVATE
recover_errors : Any -> Any
recover_errors ~body =
result = Panic.recover body
result.catch err->
Expand All @@ -19,19 +22,19 @@ recover_errors ~body =
## PRIVATE

Returns all the columns in the table, including indices.
Index columns are placed after other columns.
Index columns are placed before other columns.
Table.Table.all_columns : Vector
Table.Table.all_columns =
index = this.index.catch (_-> [])
index = this.index.catch_ []
index_columns = case index of
Vector.Vector _ -> index
_ -> [this.index]
this.columns + index_columns
a -> [a]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is strange alignment for Enso. We tend to just leave them without vertical alignment.

index_columns + this.columns


## PRIVATE

Checks if the column stores numbers.
Column.Column.is_numeric : Bool
Column.Column.is_numeric : Boolean
Column.Column.is_numeric =
[Storage.Integer,Storage.Decimal].contains this.storage_type
20 changes: 10 additions & 10 deletions distribution/std-lib/Standard/src/Visualization/Histogram.enso
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import Standard.Visualization.Helpers

## PRIVATE
Get first numeric column of the table.
first_numeric : Table -> Column ! Nothing
first_numeric table = table.columns.find _.is_numeric
Table.Table.first_numeric : Table -> Column ! Nothing
Table.Table.first_numeric = this.all_columns.find _.is_numeric

## PRIVATE
Get the value column - the column that will be used to create histogram.
value_column : Table -> Column ! Nothing
value_column table =
named_col = table.at 'value'
named_col.catch_ <| here.first_numeric table
Table.Table.value_column : Table -> Column ! Nothing
Table.Table.value_column =
named_col = this.at 'value'
named_col.catch_ <| this.first_numeric

## PRIVATE
Information that are placed in an update sent to a visualization.
Expand All @@ -25,7 +25,7 @@ type Update

## PRIVATE
Generate JSON that can be consumed by the visualization.
from_table : Object
to_json : Object
to_json =
data = ['data', Json.from_pairs [['values', this.values]]]
axis = ['axis', Json.from_pairs [['x', Json.from_pairs [['label', this.label]]]]]
Expand All @@ -35,15 +35,15 @@ type Update
Json.from_pairs ret_pairs

## PRIVATE
from_table : Vector -> Update
from_table : Table -> Update
from_table table =
col = here.value_column table
col = table.value_column
label = col.name.catch_ Nothing
values = col.to_vector.catch_ []
Update values label

## PRIVATE
from_value : Vector -> Update
from_vector : Vector -> Update
from_vector vector =
Update vector Nothing

Expand Down
60 changes: 33 additions & 27 deletions distribution/std-lib/Standard/src/Visualization/Scatter_Plot.enso
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import Standard.Visualization.Helpers

## PRIVATE
Name of the index column that may be generated to plot against.
index_name : Text
index_name = 'index'

## PRIVATE
data_field : Text
data_field = 'data'

## PRIVATE
axis_field : Text
axis_field = 'axis'

## PRIVATE
label_field : Text
label_field = 'label'

## PRIVATE

Represents a recognized point data field for a scatter plot visualization.
type PointData
## PRIVATE
Expand All @@ -39,25 +42,30 @@ type PointData

## PRIVATE
Returns all recognized point data fields.
all_fields : Vector
all_fields = [X,Y,Color,Shape,Label,Size]

## PRIVATE
recognized_names : Vector
recognized_names = this.all_fields.map _.name

## PRIVATE
is_recognized : Column -> Boolean
is_recognized column = this.recognized_names.contains column.name

## PRIVATE
name = this.to_text.to_lower_case
name : Text
name = this.to_text.to_lower_case

## PRIVATE
fallback_column : Table -> Column ! Nothing
fallback_column table = case this of
X -> table.index.catch_ <| this.iota table.row_count
Y ->
x_column = X.lookup table
candidates = table.all_columns
x_column = X.lookup_in table
candidates = table.all_columns
is_good_enough c = c.is_numeric && c.name != x_column.name
is_good c = is_good_enough c && (this.is_recognized c).not
is_good c = is_good_enough c && (this.is_recognized c).not

candidates.find is_good . catch_ <| candidates.find is_good_enough
_ -> Error.throw Nothing
Expand All @@ -72,60 +80,58 @@ type PointData
Column.from_vector here.index_name range.to_vector

## PRIVATE
lookup : Table -> Column
lookup table =
lookup_in : Table -> Column
lookup_in table =
named = table.at this.name
named.catch_ <| this.fallback_column table

## PRIVATE
Generates JSON that describes points data.
data_from_table : Table -> Object
data_from_table df =
# IO.println <| "data from table: " + df.to_text
get_point_data field = field.lookup df . rename field.name
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
(0.up_to (df.row_count + 1)).to_vector.map <| row_n->
(0.up_to <| this.row_count + 1).to_vector.map <| row_n->
pairs = columns.map column->
value = column.at row_n . catch_ Nothing
[column.name, value]
Json.from_pairs pairs

## PRIVATE
Generates JSON that describes plot axes.
axes_from_table : Table -> Object
axes_from_table table =
Table.Table.axes : Table -> Object
Table.Table.axes =
describe_axis field =
col_name = field.lookup table . name
label = Json.from_pairs [[here.label_field, col_name]]
col_name = field.lookup_in this . name
label = Json.from_pairs [[here.label_field, col_name]]
[field.name, label]
x_axis = describe_axis X
y_axis = describe_axis Y
is_valid axis_pair =
label = axis_pair.at 1
label.is_error.not && (table.all_columns.length > 0)
label.is_error.not && (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

## PRIVATE
data_from_vector : Vector -> Object
data_from_vector vec =
vec.map_with_index <| i-> elem->
Vector.Vector.point_data : Vector -> Object
Vector.Vector.point_data =
this.map_with_index <| i-> elem->
Json.from_pairs [[X.name,i],[Y.name,elem]]

## PRIVATE

json_from_table table =
data = here.data_from_table table
# IO.println <| "data: " + data.to_text
axes = here.axes_from_table table
# IO.println <| "axes: " + axes.to_text
data = table.point_data
axes = table.axes
Json.from_pairs <| [[here.data_field,data], [here.axis_field, axes]]

## PRIVATE
json_from_vector vec =
data = [here.data_field,here.data_from_vector vec]
axes = [here.axis_field,Nothing]
Json.from_pairs [data,axes]
data = [here.data_field, vec.point_data]
axes = [here.axis_field, Nothing]
Json.from_pairs [data, axes]

## PRIVATE

Expand Down
2 changes: 1 addition & 1 deletion test/Visualization_Tests/src/Helpers_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ spec =
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 ['b','a']
table.all_columns.map (_.name) . should_equal ['a','b']
11 changes: 9 additions & 2 deletions test/Visualization_Tests/src/Histogram_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import Visualization_Tests

spec =
expect value expected_label expected_values =
text = Histogram.process_to_json_text value
json = Json.parse text
text = Histogram.process_to_json_text value
json = Json.parse text
expected_data = Json.from_pairs [['values', expected_values]]
expected_json = case expected_label of
Nothing -> Json.from_pairs [['data', expected_data]]
Expand All @@ -33,6 +33,13 @@ spec =
table = Table.from_rows header [row_1, row_2]
expect table 'α' [11,21]

Test.specify "plots first column if none recognized even if index" <|
header = ['α']
row_1 = [11 ]
row_2 = [21 ]
table = Table.from_rows header [row_1, row_2] . set_index 'α'
expect table 'α' [11,21]

Test.specify "plots 'value' numeric column if present" <|
header = ['α', 'value']
row_1 = [11 , 10 ]
Expand Down