shiny 4 small textInput boxes side-by-side
RShinyR Problem Overview
I've got a shiny server version 0.4.0 and I want to have 4 small textInput boxes to look like this:
x-min x-max y-min y-max
[...] [...] [...] [...]
They now look like this:
x-min
[...................]
x-max
[...................]
y-min
[...................]
y-max
[...................]
With this code:
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5),
textInput(inputId="ylimitsmin", label="y-min", value = 0.5),
textInput(inputId="ylimitsmax", label="y-max", value = 1.0),
Any ideas how to achieve this?
EDITED: I've successfully changed things like this elsewhere in the code:
<style type="text/css">select#yaxis4 { height: 280px; width: 500px; }</style>
[... which links to this later on in the page...]
<label class="control-label" for="yaxis4">Y-Axis</label>
<select id="yaxis4" multiple="multiple">
And this is what it looks like for the ones that don't work:
<style type="text/css">select#xlimitsmax { display: inline-block; max-width: 50px; }</style>
[... which links to...]
<label>x-max</label>
<input id="xlimitsmax" type="text" value="0.5"/>
EDITED:
Here is a self contained example ui.R
that doesn't work:
library(shiny)
shinyUI(
pageWithSidebar(
# application title
headerPanel("test01"),
sidebarPanel(
tags$head(
tags$style(type="text/css", "select { max-width: 360px; }"),
tags$style(type="text/css", ".span4 { max-width: 360px; }"),
tags$style(type="text/css", ".well { max-width: 360px; }")
),
wellPanel(
p(strong("Side Panel:"))
)
),
mainPanel(
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
tags$head(tags$style(type="text/css", "select#xlimitsmin { max-width: 50px }")),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5),
tags$head(tags$style(type="text/css", "select#xlimitsmax { display: inline-block; max-width: 50px; }"))
)
))
Resulting page:
R Solutions
Solution 1 - R
to paraphrase (and to simplify to the case of 2 inputs), your problem is that:
runApp(list(
ui = bootstrapPage(
textInput(inputId="xlimitsmin", label="x-min", value = 0.0),
textInput(inputId="xlimitsmax", label="x-max", value = 0.5)
),
server = function(input, output) {}
))
shows
But you want side-by-side small inputs, like so:
The short answer
textInputRow<-function (inputId, label, value = "")
{
div(style="display:inline-block",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "text", value = value,class="input-small"))
}
runApp(list(
ui = bootstrapPage(
textInputRow(inputId="xlimitsmin", label="x-min", value = 0.0),
textInputRow(inputId="xlimitsmax", label="x-max", value = 0.5)
),
server = function(input, output) {}
))
gives:
The long answer
Side-by-side inputs
Let's do side-by-side first:
Currently textInput generates two separate tags - the label
, and the input
, each of which is configured by CSS as display:block
, which means it's a rectangle that will break to the left side of the container. We need to wrap each textInput
's field in new container (div) and tell that container that the container that follows it (the next textInput
) is allowed to be on the same horizontal row on the page, using CSS's display:inline-block
.
So we add a div with a style around each textInput
:
runApp(list(
ui = bootstrapPage(
div(style="display:inline-block",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
div(style="display:inline-block",textInput(inputId="xlimitsmax", label="x-max", value = 0.5))
),
server = function(input, output) {}
))
Small inputs
Now let's deal with small. There are a few ways to do small,
- make the font smaller,
- make the input box have fewer characters in it.
- tell css or (here) bootstrap to draw a smaller box
Since bootstrap.js
is really in control of layout when we use shiny, only 3 will reliably work, so let's use that.
Input sizes are documented in Bootstrap 2.3.2's CSS Forms documentation, under 'Control Sizing'. It includes a variety of sizes from mini, small, medium, large, xlarge, and xxlarge, and the default is probably medium. Let's try small, instead.
To set the size, we need to change the class of the input
tag generated by textInput
.
Now textInput
is just a convenience function around the more powerful tags
functions such as tags$label
and tags$input
. We can build a more powerful version of textInput
that allows us to configure the elements, specifically the class of the input
node:
textInput2<-function (inputId, label, value = "",...)
{
tagList(tags$label(label, `for` = inputId), tags$input(id = inputId,
type = "text", value = value,...))
}
runApp(list(
ui = bootstrapPage(
div(style="display:inline-block",textInput2(inputId="xlimitsmin", label="x-min", value = 0.0, class="input-small")),
div(style="display:inline-block",textInput2(inputId="xlimitsmax", label="x-max", value = 0.5, class="input-small"))
),
server = function(input, output) {}
))
And we are done - but we can roll some of this functionality up by having textInput3
generate the div tag. It could also set the class by itself, but I'll leave that for you to write.
Wrapping it up
textInput3<-function (inputId, label, value = "",...)
{
div(style="display:inline-block",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "text", value = value,...))
}
runApp(list(
ui = bootstrapPage(
textInput3(inputId="xlimitsmin", label="x-min", value = 0.0, class="input-small"),
textInput3(inputId="xlimitsmax", label="x-max", value = 0.5, class="input-small")
),
server = function(input, output) {}
))
For interest's sake, here's the version using class input-mini
:
Solution 2 - R
Using Shiny (>= 0.11), you can accomplish this by putting the input calls within a splitLayout(). This will split the fluid row, box, etc. into the necessary columns required to show your input fields side-by-side.
The example below would give you three text inputs in a box, that will appear side-by-side in the fluidRow.
fluidRow(
box(width = 12, title = "A Box in a Fluid Row I want to Split",
splitLayout(
textInput("inputA", "The first input"),
textInput("inputB", "The second input"),
textInput("inputC", "The third input")
)
)
)
Solution 3 - R
Maybe this solution wasn't around in 2013 but if you want to do this without writing HTML or CSS you can just use the column
function within a fluidRow
like so:
fluidRow(
column(3,
selectInput('pcat', 'Primary Category', c("ALL", "Some"))),
column(3,
selectInput('smodel', 'Statistical Model', c("NONE", "LINEAR REGRESSION", "LOWESS")))
)
And it will place things side by side.
EDIT: Now there is another very easy way to do this using the splitLayout()
function. See Nadir Sidi's answer for more details.
Solution 4 - R
I deleted the old answer - here is one that works:
ui.r:
library(shiny)
shinyUI(
pageWithSidebar(
# application title
headerPanel("test01"),
sidebarPanel(
tags$head(
tags$style(type="text/css", "select { max-width: 360px; }"),
tags$style(type="text/css", ".span4 { max-width: 360px; }"),
tags$style(type="text/css", ".well { max-width: 360px; }")
),
wellPanel(
p(strong("Side Panel:"))
)
),
mainPanel(
div(id="XXmin",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
tags$head(tags$style(type="text/css", "#XXmin {display: inline-block}")),
tags$head(tags$style(type="text/css", "#xlimitsmin {max-width: 50px}")),
div(id="XXmax",textInput(inputId="xlimitsmax", label="x-max", value = 0.5)),
tags$head(tags$style(type="text/css", "#XXmax {display: inline-block}"),
tags$head(tags$style(type="text/css", "#xlimitsmax {max-width: 50px}"))
))
))
Here are the changes I made:
-
I eliminated the
select
fromselect#xlimitsmax
andselect#xlimitsmin
in your.css
statements -
I put your two controls each in their own
div()
and gave them the namesXXmin
andXXmax
. I then added.css
statements to make them inline-block.
If you have a bunch of these you might want to use a class
statement - such as:
div(class="MyClass",textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
tags$head(tags$style(type="text/css", ".MyClass {display: inline-block}")),
tags$head(tags$style(type="text/css", "#xlimitsmin {max-width: 50px}")),
then you can tag each of the control div()
's as class="MyClass"
and use only one .css
statement.
Edited to add: Thanks for posting the example code - that made it much easier.
2nd Edit: Just to clarify. The point of putting the textInput
commands inside a div()
is to unite the input box and its label into a single object so that styles (in this case the display
style) can be applied. If you don't do this, the label and the box act as two separate entities and it is harder to manipulate them in cases like this.
Solution 5 - R
As an alternative to putting verbose style declarations in a class, it seems you can easily extend the shiny tags functions to your liking. This particular one would be convenient to have around by default. (this is with shiny shiny_0.14.1 ). Thought I was going to need to write a closure but this seems to work.
inline = function (x) {
tags$div(style="display:inline-block;", x)
}
inline(textInput(inputId="xlimitsmin", label="x-min", value = 0.0)),
inline(textInput(inputId="xlimitsmax", label="x-max", value = 0.5)),
inline(textInput(inputId="ylimitsmin", label="y-min", value = 0.5)),
inline(textInput(inputId="ylimitsmax", label="y-max", value = 1.0)),
Solution 6 - R
I wasn't happy with splitLayout()
because it introduces scroll bars when space is limited.
I found that, at least for input widgets like buttons or text boxes, a quite easy solution with better responsive behaviour is using flex-box: (see this great guide: https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
div(
style = "display: flex; flex-wrap: wrap;",
div(
style = "flex: 1;",
textInput("inputA", "The first input")
),
div(
style = "flex: 1;",
textInput("inputB", "The second input")
),
div(
style = "flex: 1;",
textInput("inputC", "The third input")
)
)
It is possible to adjust relative widths. Corresponding to splitLayout(cellWidths = c("25%", "75%"), ...)
:
div(
style = "display: flex; flex-wrap: wrap;",
div(
style = "flex: 1;",
textInput("inputA", "The first input")
),
div(
style = "flex: 3;", # second item 3 times as wide as first one
textInput("inputB", "The second input")
)
)
Solution 7 - R
If you want the inputs in mainPanel you can use the following:
div(class="row-fluid",
div(class="span1",textInput("xlimitsmin", label = "x-min", value = 0.0)),
div(class="span1",textInput("xlimitsmax", label = "x-max", value = 0.5)),
div(class="span1",textInput("ylimitsmin", label = "y-min", value = 0.5)),
div(class="span1",textInput("ylimitsmax", label = "y-max", value = 1.0))
)
Add:
#xlimitsmin, #xlimitsmax, #ylimitsmin, #ylimitsmax {
max-width: 25px;
}
in a css file (e.g., style.css in the www/ directory) in your app and source it from ui.R with:
includeCSS('www/style.R')
I am not sure why you need a textInput rather than a numericInput since the input you seem to be looking for is numeric. If you choose numericInput you can simply replace textInput with numericInput in the above. If you want the inputs in the sidebarPanel you could use the code below. The same css file mentioned above would be needed.
div(class="row-fluid",
div(class="span3",numericInput("xlimitsmin", label = "x-min", value = 0.0)),
div(class="span3",numericInput("xlimitsmax", label = "x-max", value = 0.5)),
div(class="span3",numericInput("ylimitsmin", label = "y-min", value = 0.5)),
div(class="span3",numericInput("ylimitsmax", label = "y-max", value = 1.0))
)
Solution 8 - R
Sgrubsmyon's approach was almost perfect for me however I ran into a new problem with the flex-box approach in that there was no padding between the inputs. Apparently this has something to do with "display: flex" being a wrapper for "flex-grow 1" which uses up all available space. I dove into the rabbit hole and couldnt get this to work but learnt about a similar approach which uses "CSS - Grid" and is even easier (relevant SO question where I learnt this from):
div(
style = "display: grid;
grid-template-columns: 20% repeat(3, 20%); ## same as repeat(4, 20%)
grid-gap: 10px;",
textInput("inputA", "The first input"),
textInput("inputB", "The second input"),
textInput("inputC", "The third input"),
textInput("inputD", "The fourth input")
)
A similar great guide exists for the CSS - Grid approach located here where you can learn about all the different arguments and customisability that you can use. Note that I have never touched CSS until 2 hours before writing this answer so any corrections are welcome =)