.. _custom_tabs:
Custom Tabs
===========
The Visualizer framework is extensible, allowing one or more
user-defined tabs to be rendered in a given instance of the Visualizer. The
Visualizer is built using the statistical programming language
`R `__ and a package called
`Shiny `__. When an instance of the
Visualizer is launched, it consumes a config file that specifies both
the data and the references to the desired tabs for that instance. Each
tab is in turn implementeded as a single Shiny module in a .R file that
includes the definition of both the UI and the backend functionality of
a tab.
Basic Tab Structure
-------------------
The basic structure of a custom tab is very simple. It must have the
following variable and function definitions present to be valid:
- ``title``: This variable should be the desired title of the tab in
the UI as a string.
- ``footer``: This variable should specify whether or not you want the
Visualizer framework footer to be visible when this tab is opened. It
will either be ``TRUE`` or ``FASLE``.
- ``ui(id)``: This function should have only the ``id`` parameter and
return the output of a Shiny UI function, e.g. fluidPage(), that
defines the desired UI.
- ``server(input, output, session, data)``: This function is passed the
following parameters:
- ``input``: This is the Shiny 'input' list. You will use this to
access inputs generated in the UI.
- ``output``: This is the Shiny 'output' list. You will use this to
assign values to outputs referenced in the UI.
- ``session``: This is the Shiny 'session' object. It is used to access
the 'ns' function and is consumed by some of the advanced Shiny
functions.
- ``data``: This data frame includes the raw data that was passed to
the Visualizer by the Results Browser as well as a host of other
relevant metadata about the dataset. See below for more infomation.
The 'data' Object
-----------------
Upon launch, the Visualizer Framework builds an R data frame that
includes the raw data and other useful metadata. The visualizer manages
this data frame for the most part, and each of the custom tabs should
only enjoy limited write-access to it.
The data object contains all the information that a tab needs to
interact with the data and any of the other features that are provided
by the Visualizer framework. Below is a mapping of the data structure
with an explanation for each of the objects.
- ``data`` - contains all the passed objects
- ``Colored`` - the filtered data that has an added ‘color’ column
- ``Filtered`` - the raw data that has been filtered by the different
UI elements in the “Filters” section
- ``Filters`` - the state of each of the sliders, selectInputs, etc. in
the “Filters” section of the Visualizer UI
- ````
- ``type`` - the “R” data-type of the variable, e.g. ‘factor,’
‘integer,’ or ‘numeric’
- ``selection`` - (if type is ‘factor’), list of all selected
choices
- ``min``, ``max`` - (if type is ‘integer’ or ‘numeric’)
- ``meta``
- ``coloring``
- ````
- ``name`` - name of the coloring scheme
- ``type`` - ‘Max/Min’ or ‘Discrete’
- ``var`` - the name of the variable that is the basis of the
coloring
- ``goal`` - (for ‘Max/Min’) ‘Maximize’ or ‘Minimize’
- ``palette`` - (for ‘Discrete’) ‘Rainbow,’ ‘Heat,’ ‘Terrain,’
‘Topo,’ or ‘Cm’
- ``rainbow_s`` - (if ‘Rainbow’ for palette) the saturation for
the palette
- ``rainbow_v`` - (if ‘Rainbow’ for palette) the value/brightness
for the palette
- ``current``
- ``name`` - name of the coloring scheme
- ``type`` - ‘Max/Min’ or ‘Discrete’
- ``var`` - the name of the variable that is the basis of the
coloring
- ``goal`` - (if type is ‘Max/Min’) ‘Maximize’ or ‘Minimize’
- ``colors`` - (if type is ‘Discrete’, list) the list of the
colors used for each variable
- ``comments`` [Not yet implemented]
- ````
- ``id`` - a guid associated with the comment
- ``username`` - the username of the user who wrote the comment
- ``date`` - the date the comment was added
- ``text`` - a guid associated with the comment
- ``object`` - (optional) the object(s) referenced in the comment
- ``pet`` - contains information about the PET that generated these
results
- ``sampling_method`` - (string) ‘Full Factorial,’ ‘Central
Composite,’ ‘Opt Latin Hypercube’, or ‘Uniform’
- ``num_samples`` - (integer) the ‘num\_samples’ value from the
‘code’ field in the OpenMETA project
- ``pet_name`` - (string) the name of the ‘Parametric Exploration’
in the OpenMETA project
- ``mga_name`` - (string) the name of the .mga file within which the
PET resides
- ``generated_configuration_model`` - (string) the name of the
‘Generated Configuration Model’ created by the DESERT tool that
was selected for the execution of this PET
- ``selected_configurations`` - (list) the names of each of the
configurations that were chosen for this PET execution
- ``design_variable_names`` - (list) the names of all variables that
were of type ‘Design Variable’
- ``design_variables`` - (list) detailed information about the
variables that were of type ‘Design Variable’
- ``objective_names`` - (list) the names of all variables that were
of type ‘Objective’
- ``pet_config`` - (data frame) the parsed pet\_config.json file.
- ``pet_config_filename`` - (string) the filename of the
'pet\_config.json' file relative to the location of the
'visualizer\_config.json' file.
- ``sets`` [Not yet implemented]
- ````
- ``name`` - name of the set
- ``username`` - the username of the user who created the set
- ``date`` - the date the set was added
- ``objects`` - the different objects in the set, most often
design configurations
- ``variables``
- ````
- ``name`` - corresponds to variable in ``data$raw`` df
- ``name_with_units`` - unit appended in parentheses
- ``type`` - 'Unknown', ‘Design Variable’, ‘Objective’, or
'Classification'
- ``username`` - the username of the user who wrote the comment
- ``date`` - the date the comment was added
- ``pre`` - basic preprocessing reactives to simplify interaction with
the data
- ``var_names()`` - (list) original names of all the variables in
the input data set
- ``var_class()`` - (list) the class (or type) of each of the
variables
- ``var_facs()`` - (list) names of all the variables of class
‘factor’
- ``var_ints()`` - (list) names of all the variables of class
‘integer’
- ``var_nums()`` - (list) names of all the variables of class
‘numeric’
- ``var_nums_and_ints()`` - (list) names of all the variables of
class ‘numeric’ or ‘integer’
- ``abs_max()``, ``abs_min()`` - (list) the maximum and minimum
values for each variable in var\_nums\_and\_ints
- ``var_range_nums_and_ints()`` - (list) names of all the variables
of class ‘numeric’ or integer’ that vary across some range, i.e.
are not constants
- ``var_range_facs()`` - (list) names of all the variables of class
‘factor’ that vary across some range, i.e. are not constants
- ``var_range()`` - (list) names of all variables that vary across
some range, i.e. are not constants
- ``var_range_nums_and_ints_list()`` - (list of lists)
``var_range_nums_and_ints()`` sorted into lists by type
- ``var_range_facs_list()`` - (list of lists) ``var_range_facs()``
sorted into lists by type
- ``var_range_list()`` - (list of lists) ``var_range()`` sorted into
lists by type
- ``var_constants()`` - (list) names of the variables of any class
that don’t vary in the dataset
- ``raw$df`` - the raw data with no filtering or coloring applied as a
reactive value
E.g. In your ``server`` function, you could find the type of the first
variable by evaluating ``data$meta$variables[[1]]$type`` in either a
reactive context or within an ``isolate()`` call. You could also
find a list of all the variables that are factors, i.e. discrete
choices, in the ``data$raw$df`` data frame by evaluating
``data$pre$var_facs()``
Histogram Example Tab
---------------------
Below is an example tab definition .R file.
.. code:: R
1|title <- "Histogram"
2|footer <- TRUE
3|
4|ui <- function(id) {
5| ns <- NS(id)
6|
7| fluidPage(
8| br(),
9| column(3,
10| selectInput(ns("variable"), "Histogram Variable:", c())
11| ),
12| column(9,
13| plotOutput(ns("plot"))
14| )
15| )
16|
17|}
18|
19|server <- function(input, output, session, data) {
20| ns <- session$ns
21|
22| observe({
23| selected <- isolate(input$variable)
24| if(is.null(selected) || selected == "") {
25| selected <- data$pre$var_range_nums_and_ints()[1]
26| }
27| saved <- si_read(ns("variable"))
28| if (is.empty(saved)) {
29| si_clear(ns("variable"))
30| } else if (saved %in% c(data$pre$var_range_nums_and_ints(), "")) {
30| selected <- si(ns("variable"), NULL)
31| }
32| updateSelectInput(session,
33| "variable",
34| choices = data$pre$var_range_nums_and_ints_list(),
35| selected = selected)
36| })
37|
38| output$plot <- renderPlot({
39| req(input$variable)
40| hist(data$Filtered()[[input$variable]],
41| main = paste("Histogram of" , paste(input$variable)),
42| xlab = paste(input$variable))
43| })
44|
45|}
The ``title`` of the tab is assigned on line 1. On line 2 we specify
that we want to display the Visualizer footer when this tab is open.
The UI for this example tab, defined in ``ui(id)`` on lines 4-17, is
simply a select box for the user to choose which variable to process for
the histogram and a placeholder for the histogram plot itself; the
select box ``inputId`` and plot ``outputId`` are 'variable' and 'plot',
respectively. The Visualizer framework implements the Shiny 'Module'
concept to isolate the tabs and avoid input name collisions; this
necessitates the ``ns <- NS(id)`` statement at the beginning of the
function and the wrapping of all the ``inputId`` and ``outputId``
parameters to Shiny UI function calls in a call to ``ns()``.
The ``server`` function, defined on lines 19-45, is where we describe
the backend processing that produces plots and other outputs for the UI.
The body of this function begins by assigning the local namespace
function (``session$ns``) to ``ns`` on line 20. Although you do not need
to call ``ns()`` when accessing variables from ``input``, e.g. the
``input$variable`` reference on line 42, you do need to wrap
``inputId``\ s and ``outputId``\ s as we did in the UI definition above
when they are being created or updated.
It then implements an ``observe()`` call on lines 22-36 to properly
update the options presented to the user in the "Histogram Variable"
select box. In Shiny, an ``observe()`` provides a mechanism for
re-running a block of code when any of the reactive variables referenced
within that code are initialized or changed. In this case we want to
update the choices presented in the 'variable' Select Input anytime the
non-constant, numeric or integer variables in our dataset change. (This
occurs when the data is initialized or classifications are added or
removed.)
This code block is fairly complex, but it provides a lot of
functionality: it specifies a default value, loads a value saved from
a previous session, and updates the 'variable' UI element dynamically as
the dataset is altered. The ``selected`` variable is first assigned the
current value of the input. This is done within an ``isolate()`` call
which breaks the reactive dependency on the input value; without the
``isolate()`` our code block would be executed every time the user
changed the input. Next we assign a default value if it is currently
null or empty, .e.g. when the Visualizer is launched for the first time.
Then we use the ``si_read()`` function to check if there is a saved
value for this input from a previous session of the visualizer. (Note
the use of the ``ns()`` call around our input name.) The ``is.empty()``
function is a custom function that evaluates to true if the value is
either null or an empty list(). To cover the case of it being an empty
list, we clear the saved value as it would prevent saving the value of
this input upon closing the current session. The final if statement
ensures that the saved choice is in the currently available options
before applying the value. Lastly we call ``updateSelectInput`` to
update the input with our new values.
The final section of code on lines 38-43 defines the 'plot' output to be a
histogram of the variable selected in the "Histogram Variable" select
box with a title and x-axis label. The ``req()`` function allows us to
break if a needed input is ``NULL`` as is the case with
``input$variable`` before the dataset is initialized and all the
reactive dependencies are sorted out.
The rendered tab looks like this:
.. image:: images/histogramTab.png
:alt: Example Histogram Tab
:width: 1110px
This example can be found at
``C:\Program Files (x86)\META\bin\Dig\tabs\Histogram.R`` (or wherever
you installed OpenMETA) and used as the basis for creating tabs of your
own.
Adding Your Own Tab
-------------------
Creating the File
~~~~~~~~~~~~~~~~~
Navigate to ``C:\Program Files (x86)\META\bin\Dig\tabs\`` to see all the
currently-configured user-defined tabs. Each file here corresponds to a
single tab in the Visualizer. To create a tab of your own, simply make a
copy of the ``Histogram.R`` (or other) file and modify it to suit
your needs. The next time you launch the Visualizer, your tab will be
included in the tabset.
.. note:: The tabs are added in the order that they appear in this
directory, so it may be useful to prepend a number to the filename.
Developing your Application
~~~~~~~~~~~~~~~~~~~~~~~~~~~
We recommend using `RStudio `__ to develop
your custom tabs. It offers syntax highlighting, code completion, and
debugging support. After downloading and installing the software, you
should be able to open the ``Dig.Rprog`` project file at
``C:\Program Files (x86)\META\bin\Dig\`` and launch the Visualizer
directly from RStudio.
To enable breakpoints in RStudio in your tab file code you will have to
comment (:kbd:`Control-Shift-C`) the ``debug`` call and uncomment the
``debugSource`` calls towards the top of ``server.R`` file.
.. code:: R
170|# Source tab files
171|print("Sourcing Tabs:")
172|tab_environments <- mapply(function(file_name, id) {
173| env <- new.env()
171| if(!is.null(visualizer_config$tab_data)) {
175| env$tab_data <- visualizer_config$tab_data[[id]]
176| } else {
177| env$tab_data <- NULL
178| }
179| # source(file_name, local = env)
180| debugSource(file_name, local = env)
181| print(paste0(env$title, " (", file_name, ")"))
182| env
183| },
184| file_name=tab_files,
185| id=tab_ids,
186| SIMPLIFY = FALSE
187|)
In some cases you may not experience proper breaking behaviour using standard
breakpoints. You can place a ``browser()`` call in your code at the location
you desire to break, and this should result in the execution pausing and an
interactive prompt being shown when the call is reached.