daattali / timevis

📅 Create interactive timeline visualizations in R
http://daattali.com/shiny/timevis-demo/
Other
655 stars 157 forks source link

Displaying duration in timeline labels #59

Closed JesseVent closed 4 years ago

JesseVent commented 6 years ago

I've recently started using the timevis package to visualise average process durations in some shiny dashboards. The issue I am having is due to measuring 'average' duration between steps in the process.

I want the timeline to display the results of duration using a measure of days instead, as the actual dates I end up with are often irrelevant and get questioned on it frequently by the dashboard consumers.

I have two questions:

I did create a function realign dates to beginning of the year, so I could reformat the date using scales::date_format("%j") which converts the day of the year as decimal number (001–366). I tried feeding the output of this into timevis and got very odd results, see below for examples and an example of the concept in ggplot.

I went through the timevis.js examples and documentation and couldn't find an answer, I'm pretty much stumped as implementing what i'm trying to do using R.

What I expect to be rendered

library(tidyverse)

# Generate timeline with unformatted dates ----------------------------------------------

df_unformatted <- data.frame(stringsAsFactors=FALSE,
  id        = 1:7,
  group     = c("customer", "customer", "customer", "initiate", "staff", "staff", "staff"),
  className = c("customer", "customer", "customer", "initiate", "staff", "staff", "staff"),
  content   = c("Provide documents", "Answer survey", "Prove identity", "Submit application",
                "Verify documents", "Verify survey", "Verify identity"),
  start     = lubridate::dmy(c("16/4/18", "12/1/18", "1/1/18", "3/4/18", "4/8/18", "16/3/18", "30/5/18")),
  end       = lubridate::dmy(c("15/5/18", "30/1/18", "13/1/18", "5/5/18", "19/9/18", "7/4/18", "6/8/18"))
)
timevis::timevis(df_unformatted)
screen shot 2018-08-15 at 10 24 18 pm

What actually renders

# Generate timeline with formatted dates ------------------------------------------------

df_formatted          <- df_unformatted
df_formatted$start    <- scales::date_format("%j")(df_formatted$start)
df_formatted$end      <- scales::date_format("%j")(df_formatted$end)
timevis::timevis(df_formatted)
screen shot 2018-08-15 at 10 25 31 pm

Concept in ggplot2

# Generate concept in ggplot ------------------------------------------------------------

time <- tibble::tibble(
    start   = df_unformatted$start,
    end     = df_unformatted$end,
    group   = df_unformatted$group,
    content = df_unformatted$content
  )
tasks <- time %>%
  mutate(start = ymd(start), end = ymd(end)) %>%
  gather(date.type, task.date, -c(group, content)) %>%
  arrange(date.type, task.date) %>%
  mutate(task = factor(content, levels = rev(unique(content)), ordered = TRUE))

ggplot(tasks, aes(x = content, y = task.date, colour = group)) +
  geom_line(size = 6) +
  guides(colour = guide_legend(title = NULL)) +
  labs(x = NULL, y = "Duration (Days)") +
  coord_flip() +
  scale_y_date(date_breaks = "4 weeks", labels = scales::date_format("%j")) +
  ggthemes::theme_few(base_size = 12, base_family = "")
screen shot 2018-08-15 at 10 26 51 pm
daattali commented 6 years ago

Hi @JesseVent , these questions don't seem like "bugs" or issues with the package. They seem like questions about the underlying visualization library. You'll need to see if what you want to do is possible with the visjs timeline library http://visjs.org/docs/timeline/ , as this package is essentially a binding to that library.

JesseVent commented 6 years ago

Thanks for the quick response, yeah I was more curious if you or any of the community has come across a similar scenario.

I'm not sure why the output is rendering like the below though - or why it doesn't appear to accept values based on vis.js documentation.

Name Type Required Description
end Date or number or string or Moment no The end date of the item. The end date is optional, and can be left null. If end date is provided, the item is displayed as a range. If not, the item is displayed as a box.
start Date or number or string or Moment yes The start date of the item, for example new Date(2010,9,23).

timevis shiny output

# timevis output
id    group className            content                    start                      end
1  1 customer  customer  Provide documents 0105-12-31T14:45:40.000Z 0134-12-31T14:45:40.000Z
2  2 customer  customer      Answer survey 2001-11-30T13:30:00.000Z                     NULL
3  3 customer  customer     Prove identity 2000-12-31T13:30:00.000Z                     NULL
4  4 initiate  initiate Submit application 1992-12-31T13:30:00.000Z 0124-12-31T14:45:40.000Z
5  5    staff     staff   Verify documents 0215-12-31T14:45:40.000Z 0261-12-31T14:45:40.000Z
6  6    staff     staff      Verify survey 1974-12-31T13:30:00.000Z 1996-12-31T13:30:00.000Z
7  7    staff     staff    Verify identity 0149-12-31T14:45:40.000Z 0217-12-31T14:45:40.000Z

timevis object

timeline <- timevis::timevis(df_formatted)
str(timeline)

# df_formatted 
A tibble: 7 x 6
     id group    className content            start   end
  <int> <chr>    <chr>     <chr>              <dbl> <dbl>
1     1 customer customer  Provide documents    106   135
2     2 customer customer  Answer survey         12    30
3     3 customer customer  Prove identity         1    13
4     4 initiate initiate  Submit application    93   125
5     5 staff    staff     Verify documents     216   262
6     6 staff    staff     Verify survey         75    97
7     7 staff    staff     Verify identity      150   218

List of 8
 $ x            :List of 8
  ..$ items     :List of 7
  .. ..$ :List of 6
  .. .. ..$ id       : chr "1"
  .. .. ..$ group    : chr "customer"
  .. .. ..$ className: chr "customer"
  .. .. ..$ content  : chr "Provide documents"
  .. .. ..$ start    : chr "106"
  .. .. ..$ end      : chr "135"
  .. ..$ :List of 6
  .. .. ..$ id       : chr "2"
  .. .. ..$ group    : chr "customer"
  .. .. ..$ className: chr "customer"
  .. .. ..$ content  : chr "Answer survey"
  .. .. ..$ start    : chr " 12"
  .. .. ..$ end      : chr " 30"
  .. ..$ :List of 6
  .. .. ..$ id       : chr "3"
  .. .. ..$ group    : chr "customer"
  .. .. ..$ className: chr "customer"
  .. .. ..$ content  : chr "Prove identity"
  .. .. ..$ start    : chr "  1"
  .. .. ..$ end      : chr " 13"
  .. ..$ :List of 6
  .. .. ..$ id       : chr "4"
  .. .. ..$ group    : chr "initiate"
  .. .. ..$ className: chr "initiate"
  .. .. ..$ content  : chr "Submit application"
  .. .. ..$ start    : chr " 93"
  .. .. ..$ end      : chr "125"
  .. ..$ :List of 6
  .. .. ..$ id       : chr "5"
  .. .. ..$ group    : chr "staff"
  .. .. ..$ className: chr "staff"
  .. .. ..$ content  : chr "Verify documents"
  .. .. ..$ start    : chr "216"
  .. .. ..$ end      : chr "262"
  .. ..$ :List of 6
  .. .. ..$ id       : chr "6"
  .. .. ..$ group    : chr "staff"
  .. .. ..$ className: chr "staff"
  .. .. ..$ content  : chr "Verify survey"
  .. .. ..$ start    : chr " 75"
  .. .. ..$ end      : chr " 97"
  .. ..$ :List of 6
  .. .. ..$ id       : chr "7"
  .. .. ..$ group    : chr "staff"
  .. .. ..$ className: chr "staff"
  .. .. ..$ content  : chr "Verify identity"
  .. .. ..$ start    : chr "150"
  .. .. ..$ end      : chr "218"
  ..$ groups    : NULL
  ..$ showZoom  : logi TRUE
  ..$ zoomFactor: num 0.5
  ..$ fit       : logi TRUE
  ..$ options   : list()
  ..$ height    : NULL
  ..$ api       : list()
 $ width        : NULL
 $ height       : NULL
 $ sizingPolicy :List of 6
  ..$ defaultWidth : NULL
  ..$ defaultHeight: NULL
  ..$ padding      : NULL
  ..$ viewer       :List of 6
  .. ..$ defaultWidth : NULL
  .. ..$ defaultHeight: NULL
  .. ..$ padding      : NULL
  .. ..$ fill         : logi TRUE
  .. ..$ suppress     : logi FALSE
  .. ..$ paneHeight   : NULL
  ..$ browser      :List of 5
  .. ..$ defaultWidth : NULL
  .. ..$ defaultHeight: NULL
  .. ..$ padding      : NULL
  .. ..$ fill         : logi FALSE
  .. ..$ external     : logi FALSE
  ..$ knitr        :List of 3
  .. ..$ defaultWidth : NULL
  .. ..$ defaultHeight: NULL
  .. ..$ figure       : logi TRUE
 $ dependencies :List of 2
  ..$ :List of 10
  .. ..$ name      : chr "jquery"
  .. ..$ version   : chr "1.11.3"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "/Library/Frameworks/R.framework/Versions/3.5/Resources/library/rmarkdown/rmd/h/jquery-1.11.3"
  .. ..$ meta      : NULL
  .. ..$ script    : chr "jquery.min.js"
  .. ..$ stylesheet: NULL
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : NULL
  .. ..$ all_files : logi TRUE
  .. ..- attr(*, "class")= chr "html_dependency"
  ..$ :List of 10
  .. ..$ name      : chr "bootstrap"
  .. ..$ version   : chr "3.3.5"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "/Library/Frameworks/R.framework/Versions/3.5/Resources/library/rmarkdown/rmd/h/bootstrap-3.3.5"
  .. ..$ meta      :List of 1
  .. .. ..$ viewport: chr "width=device-width, initial-scale=1"
  .. ..$ script    : chr [1:3] "js/bootstrap.min.js" "shim/html5shiv.min.js" "shim/respond.min.js"
  .. ..$ stylesheet: chr "css/bootstrap.min.css"
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : NULL
  .. ..$ all_files : logi TRUE
  .. ..- attr(*, "class")= chr "html_dependency"
 $ elementId    : NULL
 $ preRenderHook: NULL
 $ jsHooks      : list()
 - attr(*, "class")= chr [1:2] "timevis" "htmlwidget"
 - attr(*, "package")= chr "timevis"
JesseVent commented 6 years ago

Actually looks like 10 days ago someone added a solution https://github.com/almende/vis/issues/92#issuecomment-410534031 by using the following

options = {
    format: {
      minorLabels: function(date,scale,step) {
        // must have a space otherwise it will get mad.
        return new Date(date).getTime() + "";
      },
      majorLabels: function(date,scale,step) {
        // must have a space otherwise it will get mad.
        return new Date(date).getTime() + "";
      }
    },
    timeAxis:{scale:'millisecond'}
}
daattali commented 6 years ago

If you suspect they're weird behaviour, I suggest trying to replicate (with a minimal example) the data using the visjs javascript library directly. I'm almoat certain whatever weirdness you see is coming from visjs but if by any chance it renders well there and not here, then you can file a bug report here.

In regards to getting advice from the community, not many people would notice this issue, so I suggest posting on a forum such as the RStudio Community website, that gets very high visibility

On Aug 15, 2018 17:43, "Jesse" notifications@github.com wrote:

Thanks for the quick response, yeah I was more curious if you or any of the community has come across a similar scenario.

I'm not sure why the output is rendering like the below though - or why it doesn't appear to accept values based on vis.js documentation. Name Type Required Description end Date or number or string or Moment no The end date of the item. The end date is optional, and can be left null. If end date is provided, the item is displayed as a range. If not, the item is displayed as a box. start Date or number or string or Moment yes The start date of the item, for example new Date(2010,9,23). timevis shiny output

timevis outputid group className content start end1 1 customer customer Provide documents 0105-12-31T14:45:40.000Z 0134-12-31T14:45:40.000Z2 2 customer customer Answer survey 2001-11-30T13:30:00.000Z NULL3 3 customer customer Prove identity 2000-12-31T13:30:00.000Z NULL4 4 initiate initiate Submit application 1992-12-31T13:30:00.000Z 0124-12-31T14:45:40.000Z5 5 staff staff Verify documents 0215-12-31T14:45:40.000Z 0261-12-31T14:45:40.000Z6 6 staff staff Verify survey 1974-12-31T13:30:00.000Z 1996-12-31T13:30:00.000Z7 7 staff staff Verify identity 0149-12-31T14:45:40.000Z 0217-12-31T14:45:40.000Z

timevis object

timeline <- timevis::timevis(df_formatted) str(timeline)

df_formatted A tibble: 7 x 6

 id group    className content            start   end
1 1 customer customer Provide documents 106 1352 2 customer customer Answer survey 12 303 3 customer customer Prove identity 1 134 4 initiate initiate Submit application 93 1255 5 staff staff Verify documents 216 2626 6 staff staff Verify survey 75 977 7 staff staff Verify identity 150 218 List of 8 $ x :List of 8 ..$ items :List of 7 .. ..$ :List of 6 .. .. ..$ id : chr "1" .. .. ..$ group : chr "customer" .. .. ..$ className: chr "customer" .. .. ..$ content : chr "Provide documents" .. .. ..$ start : chr "106" .. .. ..$ end : chr "135" .. ..$ :List of 6 .. .. ..$ id : chr "2" .. .. ..$ group : chr "customer" .. .. ..$ className: chr "customer" .. .. ..$ content : chr "Answer survey" .. .. ..$ start : chr " 12" .. .. ..$ end : chr " 30" .. ..$ :List of 6 .. .. ..$ id : chr "3" .. .. ..$ group : chr "customer" .. .. ..$ className: chr "customer" .. .. ..$ content : chr "Prove identity" .. .. ..$ start : chr " 1" .. .. ..$ end : chr " 13" .. ..$ :List of 6 .. .. ..$ id : chr "4" .. .. ..$ group : chr "initiate" .. .. ..$ className: chr "initiate" .. .. ..$ content : chr "Submit application" .. .. ..$ start : chr " 93" .. .. ..$ end : chr "125" .. ..$ :List of 6 .. .. ..$ id : chr "5" .. .. ..$ group : chr "staff" .. .. ..$ className: chr "staff" .. .. ..$ content : chr "Verify documents" .. .. ..$ start : chr "216" .. .. ..$ end : chr "262" .. ..$ :List of 6 .. .. ..$ id : chr "6" .. .. ..$ group : chr "staff" .. .. ..$ className: chr "staff" .. .. ..$ content : chr "Verify survey" .. .. ..$ start : chr " 75" .. .. ..$ end : chr " 97" .. ..$ :List of 6 .. .. ..$ id : chr "7" .. .. ..$ group : chr "staff" .. .. ..$ className: chr "staff" .. .. ..$ content : chr "Verify identity" .. .. ..$ start : chr "150" .. .. ..$ end : chr "218" ..$ groups : NULL ..$ showZoom : logi TRUE ..$ zoomFactor: num 0.5 ..$ fit : logi TRUE ..$ options : list() ..$ height : NULL ..$ api : list() $ width : NULL $ height : NULL $ sizingPolicy :List of 6 ..$ defaultWidth : NULL ..$ defaultHeight: NULL ..$ padding : NULL ..$ viewer :List of 6 .. ..$ defaultWidth : NULL .. ..$ defaultHeight: NULL .. ..$ padding : NULL .. ..$ fill : logi TRUE .. ..$ suppress : logi FALSE .. ..$ paneHeight : NULL ..$ browser :List of 5 .. ..$ defaultWidth : NULL .. ..$ defaultHeight: NULL .. ..$ padding : NULL .. ..$ fill : logi FALSE .. ..$ external : logi FALSE ..$ knitr :List of 3 .. ..$ defaultWidth : NULL .. ..$ defaultHeight: NULL .. ..$ figure : logi TRUE $ dependencies :List of 2 ..$ :List of 10 .. ..$ name : chr "jquery" .. ..$ version : chr "1.11.3" .. ..$ src :List of 1 .. .. ..$ file: chr "/Library/Frameworks/R.framework/Versions/3.5/Resources/library/rmarkdown/rmd/h/jquery-1.11.3" .. ..$ meta : NULL .. ..$ script : chr "jquery.min.js" .. ..$ stylesheet: NULL .. ..$ head : NULL .. ..$ attachment: NULL .. ..$ package : NULL .. ..$ all_files : logi TRUE .. ..- attr(*, "class")= chr "html_dependency" ..$ :List of 10 .. ..$ name : chr "bootstrap" .. ..$ version : chr "3.3.5" .. ..$ src :List of 1 .. .. ..$ file: chr "/Library/Frameworks/R.framework/Versions/3.5/Resources/library/rmarkdown/rmd/h/bootstrap-3.3.5" .. ..$ meta :List of 1 .. .. ..$ viewport: chr "width=device-width, initial-scale=1" .. ..$ script : chr [1:3] "js/bootstrap.min.js" "shim/html5shiv.min.js" "shim/respond.min.js" .. ..$ stylesheet: chr "css/bootstrap.min.css" .. ..$ head : NULL .. ..$ attachment: NULL .. ..$ package : NULL .. ..$ all_files : logi TRUE .. ..- attr(*, "class")= chr "html_dependency" $ elementId : NULL $ preRenderHook: NULL $ jsHooks : list() - attr(*, "class")= chr [1:2] "timevis" "htmlwidget" - attr(*, "package")= chr "timevis" — You are receiving this because you commented. Reply to this email directly, view it on GitHub , or mute the thread .
Pindar777 commented 5 years ago

@JesseVent If these options are really working, could you please show, how to implement. I tried this in vain:

timevis(df_formatted, options = list( timeAxis = htmlwidgets::JS('{scale:"millisecond", step: 10}'), format = htmlwidgets::JS(' minorLabels: function(date,scale,step) { return new Date(date).getTime() + ""; }, majorLabels: function(date,scale,step) { return new Date(date).getTime() + ""; } }')))

daattali commented 5 years ago

@Pindar777 if you can show a working example with the javascript library, I can try to see how to make it work in timevis

Pindar777 commented 5 years ago

@daattali I'm using (time)vis via RStudio only. When reading this issue I tried to run the solution provided for the javascript library https://github.com/almende/vis/issues/92 At the moment I have no clue how to combine this into a working example with that javasript library. Hence, I cannot confirm, if the proposed solution is really working. However, modifying the GNU R example from above after consulting the help files (using htmlwidgets::JS inside the options-list) changed the output but without showing the days as timeline lables.

daattali commented 5 years ago

You can use this HTML code as a starter code to get the javascript library to work and experiment with it

<!DOCTYPE HTML>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js"></script>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="visualization"></div>
<script type="text/javascript">
  var container = document.getElementById('visualization');
  var items = [
    {id: 1, content: 'item 1', start: '2013-04-19'}
  ]
  var timeline = new vis.Timeline(container, items, {});
</script>
</body>
</html>
Pindar777 commented 5 years ago

@daattali Thanks. I'm gonna try it and check back.

Pindar777 commented 5 years ago

@daattali Well, altough trying hard to use the "format" option, is is just ignored by vis.js and the padding zeros show up as if not using the extra code.

JesseVent commented 5 years ago

I couldn’t end up getting it to work the way i intended it and ended up going a different route for displaying average times using the processanimateR package and then used timevis for drilling through to actual times. Issue was more with me trying to use timevis for duration in days from day zero which doesn’t look like it supports in vis.js

Combination worked quite well shinyapps.io/loan-process

Pindar777 commented 5 years ago

@JesseVent Thanks. This package is new to me. Good example!