mirror of
https://github.com/enso-org/enso.git
synced 2024-12-29 02:41:40 +03:00
Initial enso changes to ScatterPlot (#10871)
This commit is contained in:
parent
27f7d14140
commit
069cf61b95
@ -1,5 +1,5 @@
|
||||
from Standard.Base import all
|
||||
|
||||
import Standard.Base.Data.Vector.Builder
|
||||
from Standard.Table import Column, Table
|
||||
|
||||
import project.Helpers
|
||||
@ -33,6 +33,9 @@ type Point_Data
|
||||
## PRIVATE
|
||||
Y
|
||||
|
||||
## PRIVATE
|
||||
Y_multi number:Integer=0
|
||||
|
||||
## PRIVATE
|
||||
Color
|
||||
|
||||
@ -66,7 +69,10 @@ type Point_Data
|
||||
## PRIVATE
|
||||
fallback_column : Table -> Column ! No_Fallback_Column
|
||||
fallback_column self table = case self of
|
||||
Point_Data.X -> Point_Data.iota table.row_count
|
||||
Point_Data.X ->
|
||||
candidate = table.columns.first
|
||||
is_good c = c.is_numeric && c.name != Point_Data.Y.name
|
||||
if is_good candidate then candidate else Point_Data.iota table.row_count
|
||||
Point_Data.Y ->
|
||||
x_column = Point_Data.X.lookup_in table
|
||||
candidates = table.columns
|
||||
@ -74,6 +80,15 @@ type Point_Data
|
||||
is_good c = is_good_enough c && (self.is_recognized c).not
|
||||
|
||||
candidates.find if_missing=(Error.throw Nothing) is_good . catch_ <| candidates.find is_good_enough
|
||||
Point_Data.Y_multi _ ->
|
||||
x_column = Point_Data.X.lookup_in table
|
||||
y_column = Point_Data.Y.lookup_in table
|
||||
check_for_size c = c.name.equals_ignore_case Point_Data.Size.name != True
|
||||
candidates_filtered = table.columns.filter (c-> c.is_numeric && c.name != x_column.name && c.name != y_column.name && check_for_size c)
|
||||
candidates = candidates_filtered.drop self.number
|
||||
is_good c = (self.is_recognized c).not
|
||||
|
||||
candidates.find if_missing=candidates.first is_good
|
||||
_ -> Error.throw No_Fallback_Column
|
||||
|
||||
## PRIVATE
|
||||
@ -100,34 +115,41 @@ type No_Fallback_Column
|
||||
## PRIVATE
|
||||
|
||||
Generates JSON that describes points data.
|
||||
Table.point_data : Vector
|
||||
Table.point_data self =
|
||||
Table.point_data : Vector -> Vector
|
||||
Table.point_data self all_fields =
|
||||
get_point_data field = field.lookup_in self . rename field.name . catch Any (_->Nothing)
|
||||
is_not_nothing x = case x of
|
||||
Nothing -> False
|
||||
_ -> True
|
||||
columns = Point_Data.all_fields.map get_point_data . filter is_not_nothing
|
||||
columns = all_fields.map get_point_data . filter is_not_nothing
|
||||
(0.up_to self.row_count).to_vector.map <| row_n->
|
||||
pairs = columns.map column->
|
||||
value = column.at row_n . catch_ Nothing
|
||||
[column.name, value]
|
||||
JS_Object.from_pairs pairs
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Returns the number of numeric columns for the plot.
|
||||
Table.numeric_column_count : Number
|
||||
Table.numeric_column_count self =
|
||||
columns = self.columns . filter (c-> c.is_numeric && c.name != Point_Data.Size.name)
|
||||
columns.length
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Generates JSON that describes plot axes.
|
||||
Table.axes : JS_Object
|
||||
Table.axes self =
|
||||
Table.axes : Vector -> JS_Object
|
||||
Table.axes self all_fields =
|
||||
describe_axis field =
|
||||
col_name = field.lookup_in self . name
|
||||
label = JS_Object.from_pairs [[label_field, col_name]]
|
||||
[field.name, label]
|
||||
x_axis = describe_axis Point_Data.X
|
||||
y_axis = describe_axis Point_Data.Y
|
||||
axis = all_fields.map describe_axis
|
||||
is_valid axis_pair =
|
||||
label = axis_pair.at 1
|
||||
label.is_valid && (self.columns.length > 0)
|
||||
axes_obj = JS_Object.from_pairs <| [x_axis, y_axis].filter is_valid
|
||||
axes_obj = JS_Object.from_pairs <| axis.filter is_valid
|
||||
if axes_obj.length > 0 then axes_obj else Nothing
|
||||
|
||||
## PRIVATE
|
||||
@ -178,11 +200,22 @@ limit_data limit data = case limit of
|
||||
if limit <= extreme.length then extreme.take (..First limit) else
|
||||
extreme + data.take (..Sample (limit - extreme.length))
|
||||
|
||||
|
||||
## PRIVATE
|
||||
get_axes_field : Integer -> Vector
|
||||
get_axes_field number_of_numeric =
|
||||
fields_for_multiseries = ((0.up_to number_of_numeric).map idx-> Point_Data.Y_multi idx)
|
||||
fields_for_multiseries . insert item=Point_Data.X . insert item=Point_Data.Y
|
||||
|
||||
## PRIVATE
|
||||
json_from_table : Table -> Vector Integer | Nothing -> Integer | Nothing -> Text
|
||||
json_from_table table bounds limit =
|
||||
data = table.point_data |> bound_data bounds |> limit_data limit
|
||||
axes = table.axes
|
||||
number_of_numeric_cols = table.numeric_column_count-2
|
||||
fields_for_multiseries = Point_Data.all_fields + ((0.up_to number_of_numeric_cols).map idx-> Point_Data.Y_multi idx)
|
||||
fields_for_plot = if number_of_numeric_cols > 0 then fields_for_multiseries else Point_Data.all_fields
|
||||
data = table.point_data fields_for_plot |> bound_data bounds |> limit_data limit
|
||||
fields_for_axes = get_axes_field number_of_numeric_cols
|
||||
axes = table.axes fields_for_axes
|
||||
JS_Object.from_pairs [[data_field, data], [axis_field, axes]] . to_json
|
||||
|
||||
## PRIVATE
|
||||
@ -202,9 +235,9 @@ json_from_vector vec bounds limit =
|
||||
process_to_json_text : Any -> Integer | Nothing -> Integer | Nothing -> Text
|
||||
process_to_json_text value bounds=Nothing limit=Nothing =
|
||||
json = case value of
|
||||
_ : Column -> json_from_table value.to_table bounds limit
|
||||
_ : Table -> json_from_table value bounds limit
|
||||
_ : Vector -> json_from_vector value bounds limit
|
||||
_ : Column -> json_from_table value.to_table bounds limit
|
||||
_ -> json_from_vector value.to_vector bounds limit
|
||||
|
||||
json
|
||||
|
@ -26,22 +26,16 @@ add_specs suite_builder =
|
||||
index = Scatter_Plot.index_name
|
||||
axis label = JS_Object.from_pairs [['label',label]]
|
||||
labels x y = JS_Object.from_pairs [['x', axis x], ['y', axis y]] . to_text
|
||||
labels_multi x y z = JS_Object.from_pairs [['x', axis x], ['y', axis y], ['(y_multi 0)', axis z]] . to_text
|
||||
no_labels = 'null'
|
||||
|
||||
suite_builder.group "Scatter Plot Visualization" group_builder->
|
||||
group_builder.specify "plots first column if none recognized" <|
|
||||
group_builder.specify "plots left most column for x if numeric" <|
|
||||
header = ['α', 'ω']
|
||||
row_1 = [11 , 10 ]
|
||||
row_2 = [21 , 20 ]
|
||||
table = Table.from_rows header [row_1, row_2]
|
||||
expect table (labels index 'α') '[{"x":0,"y":11},{"x":1,"y":21}]'
|
||||
|
||||
group_builder.specify "plots 'y' against indices when no 'x' recognized" <|
|
||||
header = ['α', 'y']
|
||||
row_1 = [11 , 10 ]
|
||||
row_2 = [21 , 20 ]
|
||||
table = Table.from_rows header [row_1, row_2]
|
||||
expect table (labels index 'y') '[{"x":0,"y":10},{"x":1,"y":20}]'
|
||||
expect table (labels 'α' 'ω') '[{"x":11,"y":10},{"x":21,"y":20}]'
|
||||
|
||||
group_builder.specify "recognizes all relevant columns" <|
|
||||
header = ['x' , 'y' , 'size' , 'shape' , 'label' , 'color' ]
|
||||
@ -59,7 +53,7 @@ add_specs suite_builder =
|
||||
header = ['x' , 'size' , 'name' , 'z' , 'ω']
|
||||
row_1 = [11 , 50 , 'circul' , 20 , 30]
|
||||
table = Table.from_rows header [row_1]
|
||||
expect table (labels 'x' 'z') '[{"size":50,"x":11,"y":20}]'
|
||||
expect table (labels_multi 'x' 'z' 'ω') '[{"size":50,"x":11,"y":20,"(y_multi 0)":30}]'
|
||||
|
||||
group_builder.specify "provided only recognized columns" <|
|
||||
header = ['x', 'y' , 'bar' , 'size']
|
||||
@ -115,10 +109,6 @@ add_specs suite_builder =
|
||||
text = Scatter_Plot.process_to_json_text vector bounds
|
||||
expect_text text no_labels '[{"x":1,"y":10},{"x":2,"y":20}]'
|
||||
|
||||
group_builder.specify "using indices for x if given a column" <|
|
||||
column = Column.from_vector 'some_col' [10,2,3]
|
||||
expect column (labels 'index' 'some_col') '[{"x":0,"y":10},{"x":1,"y":2},{"x":2,"y":3}]'
|
||||
|
||||
group_builder.specify "using indices for x if given a range" <|
|
||||
value = 2.up_to 5
|
||||
expect value no_labels '[{"x":0,"y":2},{"x":1,"y":3},{"x":2,"y":4}]'
|
||||
|
Loading…
Reference in New Issue
Block a user