Open jrchudy opened 1 year ago
For example, violin plot will have the following configuration for the current set of hardcoded user controls:
layout: [{
component: 'gene',
x: 0, y: 0, w: 1, h: 1,
static: true
}, {
component: 'groupby',
x: 1, y: 0, w: 1, h: 1,
static: true
}, {
component: 'scale',
x: 2, y: 0, w: 1, h: 1,
static: true
}, {
component: 'study',
x: 0, y: 1, w: 3, h: 3,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 3,
row_height: 30
},
user_controls: [{
uid: 'gene',
label: 'Gene',
type: 'facet-search-popup',
url_param_key: 'Gene',
compact: true,
request_info: {
url_pattern: '/ermrest/catalog/2/entity/RNASeq:Replicate_Expression/{{#if (gt $control_values.study.length 0)}}{{#each $control_values.study}}Study={{{this.values.RID}}}{{#unless @last}};{{/unless}}{{/each}}/{{/if}}(NCBI_GeneID)=(Common:Gene:NCBI_GeneID)',
value_key: 'NCBI_GeneID',
selected_value_pattern: '{{{$self.values.NCBI_Symbol}}}'
}
}, {
uid: 'groupby',
label: 'Group By',
type: 'dropdown',
request_info: {
data: [
{
Name: "Experiment"
Display: "Experiment"
}, {
Name: 'Experiment_Internal_ID',
Display: 'Experiment Internal ID'
}, ...
],
default_values: 'Experiment',
value_key: 'Name',
selected_value_pattern: '{{{$self.values.Display}}}',
tick_markdown_pattern: '{{{$self.values.Experiment}}}',
}
}, {
uid: 'scale',
label: 'Scale',
type: 'dropdown',
request_info: {
data: [
{
Name: 'Linear'
}, {
Name: 'Log'
}
],
default_values: 'Linear',
value_key: 'Name',
selected_value_pattern: '{{{$self.values.Name}}}'
}
}, {
uid: 'study',
label: 'Study',
type: 'button-facet-search-popup',
url_param_key: 'Study',
request_info: {
url_pattern: '/ermrest/catalog/2/entity/RNASeq:Replicate_Expression/NCBI_GeneID={{{$control_values.gene.values.NCBI_GeneID}}}/(Study)=(RNASeq:Study:RID)',
value_key: 'RID',
selected_value_pattern: {{{$self.values.RID}}},
wait_for: 'gene'
}
}
}
},
config: {
xaxis: {
title_markdown_pattern: '{{{$control_values.groupby.values.Display}}}'
},
yaxis: {
type_pattern: '{{{$control_values.scale.values.Name}}}'
}
},
traces: [{
url_pattern: '/ermrest/catalog/2/attributegroup/M:=RNASeq:Replicate_Expression/{{#if (gt $control_values.study.length 0)}}({{#each $control_values.study}}Study={{{this.values.RID}}}{{#unless @last}};{{/unless}}{{/each}})&{{/if}}NCBI_GeneID={{{$control_values.gene.values.NCBI_GeneID}}}/$M/Anatomical_Source,Experiment,Experiment_Internal_ID,NCBI_GeneID,Replicate,Sex,Species,Specimen,Specimen_Type,Stage,Age,Starts_At,Ends_At,TPM'
x_col_pattern: ['{{{$control_values.groupby.values.Name']
}]
The types of selectors we need to support are the following (with more details about each one below):
facet-search-popup
: control "looks" like a dropdown but clicking on the input (or button) opens a recordset single select modal
button-facet-search-popup
: control with Select All
and Select Some
buttons
Select Some
opens a recordset multi select modal... Show more
| ... Show less
dropdown
: simple control that affects how the values for x, y, or z are aggregated
request_info.data
) or fetched from the server (request_info.uri_pattern
)
request_info.uri_pattern
and request_info.data
are defined, data from the query takes precedencesimple-search-dropdown
: used in matrix app to search through the set of displayed labels with typeahead results showing in dropdown as user types in the search inputcheckbox-list
: could be used to replace the legend and allow user to select multiple options to only show in the plot The only property that needs to be defined a specific way is type
, the rest are for configuring how the control behaves.
{
type: 'facet-search-popup',
uid,
label,
url_param_key,
compact,
request_info: {
url_pattern,
default_values,
value_key,
selected_value_pattern,
tick_markdown_pattern,
wait_for
}
}
The only property that needs to be defined a specific way is type
, the rest are for configuring how the control behaves.
{
type: 'button-facet-search-popup',
uid,
label,
url_param_key,
compact,
request_info: {
uri_pattern,
default_values,
value_key,
selected_value_pattern,
tick_markdown_pattern,
wait_for
}
}
2 properties defined along with type
include action
and axis
. These tell the app what action will be applied and to what axis when a selection is made in the dropdown
axis
is the axis that is affected by the control
x_col_pattern
, y_col_pattern
, and z_col_pattern
in traces[]
request_info
is for the data that is used to populate the input
request_info.url_pattern
will be used instead of data
{
type: 'dropdown',
uid,
label,
request_info: {
url_pattern,
data: [
{ value, label },
...
],
default_values,
value_key,
selected_value_pattern,
tick_markdown_pattern,
wait_for
}
This is the type of input that is used on the top left of the mouse matrix app. One way this input could work is to load all of the data from request_info.data
or request_info.url_pattern
into the list that the search box searches through. Upon selection, this could affect the way an axis is scaled, what data is used for grouping one of the axes (x, y, or z), or sending a request that fetches new data for the plot.
request_info
behaves the same way as the above dropdown
cases
{
type: 'typeahead-search',
uid,
label,
url_param_key,
request_info: {
url_pattern,
data: [
{ value, label, default },
...
],
default_values,
value_key,
selected_value_pattern,
tick_markdown_pattern,
wait_for
}
}
This input is meant to mirror what was designed for CFDE charts on the personal dashboard page. It can be used for data from a related table (foreign key) or for simpler sets of data that allow for multiple choices
{
type: 'checkbox',
uid,
label,
url_param_key,
request_info: {
url_pattern,
data: [
{ value, label, default },
...
],
default_values,
value_key,
selected_value_pattern,
tick_markdown_pattern,
wait_for
}
}
The following control is an example for changing the host for bar plot on Gudmap homepage to show gudmap or RBK data
plots: [{
plotly: {...},
layout: [{
component: 'host',
x: 2, y: 0, w: 1, h: 1,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 3,
row_height: 30
},
user_controls: [{
uid: 'host',
label: 'Host',
type: 'dropdown',
request_info: {
url_pattern: "ermrest/catalog/2/entity/Common:Consortium",
data: [{
Name: "RBK",
Description: "The host RBK",
...
}, {
Name: "Gudmap",
Description: "..."
}],
default_values: "RBK",
value_key: "Name",
selected_value_pattern: "{{{$self.values.Name}}}: {{{$self.values.Description}}}",
}
}],
traces: [{
url_pattern: '/ermrest/catalog/2/entity/M:=Dashboard:Release_Status/Consortium={{{$control_values.host.values.Name}}}/!(%23_Released=0)/!(Data_Type=Antibody)/!(Data_Type::regexp::Study%7CExperiment%7CFile)'
}]
}, ... ]
The following control is an example for changing the range of data for bar plot on Gudmap homepage to show all data that was created or released, or only data from the last year that was created or released
plots: [{
plotly: {...},
layout: [{
component: 'range',
x: 2, y: 0, w: 1, h: 1,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 3,
row_height: 30
},
user_controls: [{
uid: 'range',
label: 'Resource Range',
type: 'dropdown',
request_info: {
data: [{
Name: '#_Released',
Display: '# Released'
}, {
Name: '#_Created',
Display: '# Created'
}, {
Name: '#_Released_in_Latest_Year',
Display: '# Released in Last Year'
}, {
Name: '#_Created_in_Latest_Year',
Display: '# Created in Last Year'
}],
default_values: '#_Released',
value_key: 'Name',
selected_value_pattern: '{{{$self.values.Display}}}',
}
}],
traces: [{
url_pattern: '/ermrest/catalog/2/entity/M:=Dashboard:Release_Status/Consortium=GUDMAP/!(%23_Released=0)/!(Data_Type=Antibody)/!(Data_Type::regexp::Study%7CExperiment%7CFile)/$M@sort(ID::desc::)?limit=26',
legend: ["#_Released"], // name of traces in legend
x_col_pattern: ['{{{$control_values.range.values.Name}}}'],
y_col_pattern: ['Data_Type'],
...
}]
}, ... ]
x_col_pattern
and y_col_pattern
to allow for dynamically choosing how to group data for x or y'gudmap-release-all': {
plots: [{
plotly: { ... },
layout: [{
component: 'scale1',
x: 2, y: 0, w: 1, h: 1,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 3,
row_height: 30
},
user_controls: [{
uid: 'scale1',
label: 'Scale',
type: 'dropdown',
request_info: {
data: [
{ Name: 'Linear' },
{ Name: 'Log' }
],
default_values: 'Linear',
value_key: 'Name',
selected_value_pattern: '{{{$self.values.Name}}}'
}
}],
config: {
xaxis: {
type_pattern: '{{{$control_values.scale1.values.Name}}}'
}
}
traces: [{ ... }]
}, {
plotly: { ... },
layout: [{
component: 'scale2',
x: 2, y: 0, w: 1, h: 1,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 3,
row_height: 30
},
user_controls: [{
uid: 'scale2',
label: 'Scale',
type: 'dropdown',
request_info: {
data: [
{ Name: 'Linear' },
{ Name: 'Log' }
],
default_values: 'Linear',
value_key: 'Name',
selected_value_pattern: '{{{$self.values.Name}}}'
}
}],
config: {
xaxis: {
type_pattern: '{{{$control_values.scale2.values.Name}}}'
}
}
traces: [{ ... }]
}]
}
adding support for type_pattern
to allow for overriding plotly.layout.xaxis.type
'gudmap-release-all': {
layout: [{
component: 'scale',
x: 2, y: 0, w: 1, h: 1,
static: true
}, {
component: 'plot1',
x: 0, y: 1, w: 3, h: 6,
static: true
}, {
component: 'plot2',
x: 0, y: 7, w: 3, h: 6,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 3,
row_height: 30
},
user_controls: [{
uid: 'scale',
label: 'Scale',
type: 'dropdown',
request_info: {
data: [
{ Name: 'Linear' },
{ Name: 'Log' }
],
default_values: 'Linear',
value_key: 'Name',
selected_value_pattern: '{{{$self.values.Name}}}'
}
}],
plots: [{
uid: 'plot1',
plotly: { ... },
config: {
xaxis: {
type_pattern: '{{{$control_values.scale.values.Name}}}'
}
}
traces: [{ ... }]
}, {
uid: 'plot2',
plotly: { ... },
config: {
xaxis: {
type_pattern: '{{{$control_values.scale.values.Name}}}'
}
}
traces: [{ ... }]
}]
}
plots: [{
plotly: {...},
layout: [{
component: 'checkbox',
x: 1, y: 0, w: 2, h: 2,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 3,
row_height: 30
},
user_controls: [{
uid: 'checkbox',
label: 'Study',
type: 'checkbox',
url_param_key: 'Study',
request_info: {
url_pattern: '/ermrest/catalog/2/entity/RNASeq:Replicate_Expression/NCBI_GeneID={{{$control_values.gene.values.NCBI_GeneID}}}/(Study)=(RNASeq:Study:RID)',
value_key: 'RID',
selected_value_pattern: {{{$self.values.RID}}},
wait_for: 'gene'
}
}],
traces: [{
url_pattern: '...'
}]
}, ... ]
From further investigation into vitessce, we determined they are using a library called ReactGridLayout
to handle the responsiveness and grid like layout. We are going to use that same library to simplify what we are doing and provide a configuration language to pass directly to that component.
The list of grid layout props can be found at this link. The ones that we want to consider allowing data modelers to configure are the following:
width
- size in pixelsauto_size
- height grows and shrinks to fit contentsbreakpoints
- available when using the response grid layout component, map to identify when a different "grid size" should be used
{lg: 1200, md: 996, sm: 768, xs: 480}
cols
- number of columns to use in the layout
layout
- array of objects for positioning each grid component
layouts
is another property that allows for a map to be defined if using breakpointsmargin
- margin between grid components, provided as [x, y] in pixels
{lg: [10, 10], md: [10, 10], ... }
that allows for more configuration based on screen sizecontainer_padding
- padding inside of each grid component container, provided as [x, y] in pixels
row_height
- height of each row in pixels
is_draggable
- can grid components be moved aroundis_resizable
- can grid components be resizedThe following properties are the ones that are defined on the objects in the layout
property above:
i
- string that corresponds to the component key in the grid (used in the code)x, y, w, h
- all numbers defined in grid unitsmin_w, max_w, min_h, max_h
- all numbers defined in grid units
static
- if true, implies is_draggable: false
and is_resizeable: false
is_draggable
- overrides static
is_resizable
- overrides static
w
or h
) span margins, so their "calculated height" is a function of row_height, # of rows (h
), and the defined marginHeight
row_height=30
, margin=[10,10]
and a unit with height 4, the calculation is (30 4) + (10 3)row_height
they pass to the responsive grid layout component that comes from ReactGridLayout
. We might want to do something similar to have a "responsive" height based on the number of rows defined in the configuration
/**
.containerHeight()
function.
The types of inputs that we need to support are the following (with more details about each below):
range-picker
: control similar to the implementation in chaise for integer, float, date, and timestamp facets
search-text
: control similar to search on recordset pages
table_column
is specifiedtext
: link to navigate to another page, similar to link at top right of study-violin for removing query parameters or pointing to another app {
type: 'range-picker',
uid,
label,
table_column,
url_param_key
}
table_column
is required to be defined so a proper facets
blob can be appended to traces[0].uri_pattern
based on the values in the min and max inputsurl_param_key
s are defined so min and max can be initialized via url
The following control is an example for changing the range of data for bar plot on Gudmap homepage to provide a different way to filter the data. Instead of based on specific column values being returned that have a summary of counts, this will filter the data based on the range over RCT
column:
plots: [{
plotly: {...},
layout: [{
component: 'rct-range',
x: 0, y: 0, w: 2, h: 2,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 2,
row_height: 30
},
user_controls: [{
uid: 'rct-range',
type: 'range-picker',
label: 'Filter by date created',
table_column: 'RCT',
url_param_key: {
min: 'rct-min',
max: 'rct-max'
}
}],
traces: [{
url_pattern: '/ermrest/catalog/2/entity/M:=Dashboard:Release_Status/Consortium=GUDMAP/!(%23_Released=0)/!(Data_Type=Antibody)/!(Data_Type::regexp::Study%7CExperiment%7CFile)/$M@sort(ID::desc::)?limit=26',
}]
}]
{
type: 'search',
uid,
label,
table_column
}
The following control is an example for filtering the content of the specimen table by doing string matching for any column in the specimen table
plots: [{
plotly: {...},
layout: [{
component: 'search',
x: 0, y: 0, w: 1, h: 1,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 2,
row_height: 30
},
user_controls: [{
uid: 'search',
type: 'search',
label: 'Filter through search',
}],
traces: [{
url_pattern: '/ermrest/catalog/2/attributegroup/M:=Gene_Expression:Specimen/stage:=left(Stage_ID)=(Vocabulary:Developmental_Stage:ID)/$M/Assay_Type,stage:Name',
}]
}]
This can be a static or dynamic link that uses templating based on $url_parameters
or $control_values
. Instead of having a "label_markdown_pattern" and the displayed "control value" (link), the label will be used as the control_value.
{
type: 'text',
uid,
label_markdown_pattern,
}
plots: [{
plotly: {...},
layout: [{
component: 'link',
x: 3, y: 0, w: 1, h: 1,
static: true
}],
grid_layout_config: {
auto_size: true,
cols: 4,
row_height: 30
},
user_controls: [{
uid: 'link',
type: 'text',
label_markdown_pattern: '/chaise/record/#1/Common:Gene/RID={{{$control_values.gene.values.RID}}}',
}, {
uid: 'gene',
label: 'Gene',
type: 'facet-search-popup',
request_info: {
url_pattern: '/ermrest/catalog/2/entity/RNASeq:Replicate_Expression/(NCBI_GeneID)=(Common:Gene:NCBI_GeneID)',
default_value: '11669',
value_key: 'NCBI_GeneID',
selected_value_pattern: '{{{$self.values.NCBI_Symbol}}}'
}
}],
traces: [{
url_pattern: '...'
}]
}]
List of what is implemented that is described in this issue:
Based on what is said in the original body of this issue
position
layouts
, layout
will be ignoredBased on what is said in the original body of this issue
layouts
moved into grid_layout_configuration
For each user control, following what was defined in the original body of this issue
wait_for
and tick_markdown_pattern
dropdown
facet search popup
markdown
text
in this issue but changed to markdown to be more accurate
Currently only the violin plot has selectors that show above the plot that are hardcoded. If violin plot wants to be used for a different use case, some of these user controls might not make sense. We also have a use case to add a user control specifically for the
heatmap
plot type that allows for the column that is used for aggregation to be changed so the plot can show different "z values" for the same set of x/y data (issue #144 ).The following properties are how we will add support for configurable user controls in plot-config:
Each individual
control
will be configured with the following:Each individual
layout
will be configured with the following:Notes about configuration language:
grid_layout_config
,user_controls
, andlayout
are repeated at the top level of the plot-config object and inside of each object inplots
.plots
has auid
for referencing the plot in the global config languagelayout
, not all properties are requiredmin_w, min_h, max_w, max_h
are only used for resizing, they have no effect if resizing is disabledgrid_layout_config
is intended to match the properties passed directly toReactGridLayout
componentposition
is only used for "local layout" when trying to position user controls relative to the plot definition the layout is defined inside ofbreakpoints
is used withResponsiveGridLayout
component when width is not seturl_param_key
allows for the initial value for a user control to be set and used for templating as a url parameterkey
in the url?< url_param_key >=<url_value>
range-picker
has a min and max value that could both be initialized via url$control_values.<control.uid>.values.<url_param_key> = <url_value>
type
can be single or multiple select depending on the control that is used, the following are the types for selectors and controlsfacet-search-popup
- recordset popup with single selectsimple-search-dropdown
- dropdown with typeahead search and loading more optionsdropdown
- dropdown with options in it. Currently Group By and Log dropdowns instudy-violin
checkbox-list-single
- a list of options that can be used to filter the datafacet-search-popup-multiselect
- recordset popup with single selectsimple-search-dropdown-multiselect
- dropdown with typeahead search and loading more optionsdropdown-multiselect
- dropdown with options in it that allows multiple selectionsbutton-facet-search-popup
- button withSelect Some
andSelect All
that opens a facet search popup. Currently the Study selector forstudy-violin
checkbox-list
- a list of options that can be used to filter the data. Similar to a facet list without a search boxrange-picker
- similar to the facet control in chaise for integer, float, and date facetssearch-box
- similar control to what we have on recordset pagerequest_info
is how the user control is initializedurl_pattern
is used by default to load data for the selectordata
is used when no query pattern is present or the request forquery_pattern
is empty or throws an errorwait_for
is used to communicate when one user control relies on data from anotherselected_value_pattern
will be applied to all selected values for the given user control when the controltype
supports selecting multiple options at once$control_values.<uid>
is how the selected value objects are stored$control_values.<uid>[0].values.<column_name>
, but wouldn't be smart enough for controls with multiple values$self.values
as a way to reference each selection for the controlGeneral Notes:
SelectData
objectsSelectData
needs to betyped
(see TODO in the code)recordsetProps
needs to be thought about whether it is fine how it is or if more of it needs to be "configurable"