Skip to content

Commit 17cdeec

Browse files
committed
Add Date and POSIXt support to sliders
1 parent 3446afd commit 17cdeec

11 files changed

+194
-55
lines changed

R/input-slider.R

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#' value doesn't fit between \code{min} and \code{max}.
1212
#' @param step Specifies the interval between each selectable value on the
1313
#' slider (if \code{NULL}, a heuristic is used to determine the step size).
14+
#' If the values are dates, \code{step} is in days; if the values are times
15+
#' (POSIXt), \code{step} is in seconds.
1416
#' @param round \code{TRUE} to round all values to the nearest integer;
1517
#' \code{FALSE} if no rounding is desired; or an integer to round to that
1618
#' number of digits (for example, 1 will round to the nearest 10, and -2 will
@@ -45,18 +47,49 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
4547
version = "0.10.2.2")
4648
}
4749

48-
# Auto step size
49-
range <- max - min
50-
if (is.null(step)) {
51-
# If short range or decimals, use means continuous decimal with ~100 points
50+
# If step is NULL, use heuristic to set the step size.
51+
findStepSize <- function(min, max, step) {
52+
if (!is.null(step)) return(step)
53+
54+
range <- max - min
55+
# If short range or decimals, use continuous decimal with ~100 points
5256
if (range < 2 || hasDecimals(min) || hasDecimals(max)) {
5357
step <- pretty(c(min, max), n = 100)
54-
step <- step[2] - step[1]
58+
step[2] - step[1]
5559
} else {
56-
step <- 1
60+
1
5761
}
5862
}
5963

64+
if (inherits(min, "Date")) {
65+
if (!inherits(max, "Date") || !inherits(value, "Date"))
66+
stop("`min`, `max`, and `value must all be Date or non-Date objects")
67+
dataType <- "date"
68+
69+
} else if (inherits(min, "POSIXt")) {
70+
if (!inherits(max, "POSIXt") || !inherits(value, "POSIXt"))
71+
stop("`min`, `max`, and `value must all be POSIXt or non-POSIXt objects")
72+
dataType <- "datetime"
73+
74+
} else {
75+
dataType <- "number"
76+
}
77+
78+
step <- findStepSize(min, max, step)
79+
80+
if (dataType %in% c("date", "datetime")) {
81+
to_ms <- function(x) 1000 * as.numeric(as.POSIXct(x))
82+
83+
# Convert values to milliseconds since epoch (this is the value JS uses)
84+
# Find step size in ms
85+
step <- to_ms(max) - to_ms(max - step)
86+
min <- to_ms(min)
87+
max <- to_ms(max)
88+
value <- to_ms(value)
89+
}
90+
91+
range <- max - min
92+
6093
# Try to get a sane number of tick marks
6194
if (ticks) {
6295
n_steps <- range / step
@@ -87,7 +120,8 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
87120
`data-prefix` = pre,
88121
`data-postfix` = post,
89122
`data-keyboard` = TRUE,
90-
`data-keyboard-step` = step / (max - min) * 100
123+
`data-keyboard-step` = step / (max - min) * 100,
124+
`data-data-type` = dataType
91125
))
92126

93127
# Replace any TRUE and FALSE with "true" and "false"

R/server-input-handlers.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ registerInputHandler("shiny.date", function(val, ...){
9595
as.Date(unlist(datelist))
9696
})
9797

98+
registerInputHandler("shiny.datetime", function(val, ...){
99+
# First replace NULLs with NA, then convert to POSIXct vector
100+
times <- lapply(val, function(x) {
101+
if (is.null(x)) NA
102+
else x
103+
})
104+
as.POSIXct(unlist(times), origin = "1970-01-01", tz = "UTC")
105+
})
106+
98107
registerInputHandler("shiny.action", function(val, ...) {
99108
# mark up the action button value with a special class so we can recognize it later
100109
class(val) <- c(class(val), "shinyActionButtonValue")

inst/www/shared/shiny.js

Lines changed: 68 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.min.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/sliderInput.Rd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ create a double-ended range slider. A warning will be issued if the
2727
value doesn't fit between \code{min} and \code{max}.}
2828
2929
\item{step}{Specifies the interval between each selectable value on the
30-
slider (if \code{NULL}, a heuristic is used to determine the step size).}
30+
slider (if \code{NULL}, a heuristic is used to determine the step size).
31+
If the values are dates, \code{step} is in days; if the values are times
32+
(POSIXt), \code{step} is in seconds.}
3133
3234
\item{round}{\code{TRUE} to round all values to the nearest integer;
3335
\code{FALSE} if no rounding is desired; or an integer to round to that

srcjs/input_binding_date.js

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ $.extend(dateInputBinding, {
1010
// format like mm/dd/yyyy)
1111
getValue: function(el) {
1212
var date = $(el).find('input').data('datepicker').getUTCDate();
13-
return this._formatDate(date);
13+
return formatDateUTC(date);
1414
},
1515
// value must be an unambiguous string like '2001-01-01', or a Date object.
1616
setValue: function(el, value) {
@@ -30,8 +30,8 @@ $.extend(dateInputBinding, {
3030

3131
// Stringify min and max. If min and max aren't set, they will be
3232
// -Infinity and Infinity; replace these with null.
33-
min = (min === -Infinity) ? null : this._formatDate(min);
34-
max = (max === Infinity) ? null : this._formatDate(max);
33+
min = (min === -Infinity) ? null : formatDateUTC(min);
34+
max = (max === Infinity) ? null : formatDateUTC(max);
3535

3636
// startViewMode is stored as a number; convert to string
3737
var startview = $input.data('datepicker').startViewMode;
@@ -106,18 +106,6 @@ $.extend(dateInputBinding, {
106106
this._setMin($input[0], $input.data('min-date'));
107107
this._setMax($input[0], $input.data('max-date'));
108108
},
109-
// Given a Date object, return a string in yyyy-mm-dd format, using the
110-
// UTC date. This may be a day off from the date in the local time zone.
111-
_formatDate: function(date) {
112-
if (date instanceof Date) {
113-
return date.getUTCFullYear() + '-' +
114-
padZeros(date.getUTCMonth()+1, 2) + '-' +
115-
padZeros(date.getUTCDate(), 2);
116-
117-
} else {
118-
return null;
119-
}
120-
},
121109
// Given a format object from a date picker, return a string
122110
_formatToString: function(format) {
123111
// Format object has structure like:

srcjs/input_binding_daterange.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ $.extend(dateRangeInputBinding, dateInputBinding, {
1010
var start = $inputs.eq(0).data('datepicker').getUTCDate();
1111
var end = $inputs.eq(1).data('datepicker').getUTCDate();
1212

13-
return [this._formatDate(start), this._formatDate(end)];
13+
return [formatDateUTC(start), formatDateUTC(end)];
1414
},
1515
// value must be an array of unambiguous strings like '2001-01-01', or
1616
// Date objects.
@@ -44,8 +44,8 @@ $.extend(dateRangeInputBinding, dateInputBinding, {
4444

4545
// Stringify min and max. If min and max aren't set, they will be
4646
// -Infinity and Infinity; replace these with null.
47-
min = (min === -Infinity) ? null : this._formatDate(min);
48-
max = (max === Infinity) ? null : this._formatDate(max);
47+
min = (min === -Infinity) ? null : formatDateUTC(min);
48+
max = (max === Infinity) ? null : formatDateUTC(max);
4949

5050
// startViewMode is stored as a number; convert to string
5151
var startview = $startinput.data('datepicker').startViewMode;

srcjs/input_binding_slider.js

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,42 @@ $.extend(sliderInputBinding, textInputBinding, {
77

88
return $(scope).find('input.js-range-slider');
99
},
10+
getType: function(el) {
11+
var dataType = $(el).data('data-type');
12+
if (dataType === 'date')
13+
return 'shiny.date';
14+
else if (dataType === 'datetime')
15+
return 'shiny.datetime';
16+
else
17+
return false;
18+
},
1019
getValue: function(el) {
20+
var $el = $(el);
1121
var result = $(el).data('ionRangeSlider').result;
22+
23+
// Function for converting numeric value from slider to appropriate type.
24+
var convert;
25+
var dataType = $el.data('data-type');
26+
if (dataType === 'date') {
27+
convert = function(val) {
28+
return formatDateUTC(new Date(+val));
29+
};
30+
} else if (dataType === 'datetime') {
31+
convert = function(val) {
32+
// Convert ms to s
33+
return +val / 1000;
34+
};
35+
} else {
36+
convert = function(val) { return +val; };
37+
}
38+
1239
if (this._numValues(el) == 2) {
13-
return [+result.from, +result.to];
40+
return [convert(result.from), convert(result.to)];
1441
}
1542
else {
16-
return +result.from;
43+
return convert(result.from);
1744
}
45+
1846
},
1947
setValue: function(el, value) {
2048
var slider = $(el).data('ionRangeSlider');
@@ -68,7 +96,24 @@ $.extend(sliderInputBinding, textInputBinding, {
6896
getState: function(el) {
6997
},
7098
initialize: function(el) {
71-
$(el).ionRangeSlider();
99+
var opts = {};
100+
var $el = $(el);
101+
var dataType = $el.data('data-type');
102+
103+
// Set up formatting functions
104+
if (dataType === 'date') {
105+
opts.prettify = function(num) {
106+
return formatDateUTC(new Date(num));
107+
};
108+
109+
} else if (dataType === 'datetime') {
110+
opts.prettify = function(num) {
111+
var date = new Date(num);
112+
return date.toUTCString();
113+
};
114+
}
115+
116+
$el.ionRangeSlider(opts);
72117
},
73118

74119
// Number of values; 1 for single slider, 2 for range slider

0 commit comments

Comments
 (0)