From 6fbc9cb5e3031aae7dbc2b084ad7c19d8c1497d3 Mon Sep 17 00:00:00 2001 From: Felix Mannhardt Date: Mon, 19 Nov 2018 15:43:06 +0100 Subject: [PATCH] Rename Slider class to PlaybackControl, Added time reporting back to Shiny --- R/processanimateR.R | 3 +- .../lib/modules/animation_slider.js | 179 ------------------ inst/htmlwidgets/processanimateR.js | 38 +--- inst/htmlwidgets/processanimateR.yaml | 4 +- vignettes/use-shiny-selections.Rmd | 14 +- 5 files changed, 25 insertions(+), 213 deletions(-) delete mode 100644 inst/htmlwidgets/lib/modules/animation_slider.js diff --git a/R/processanimateR.R b/R/processanimateR.R index 4f3ca7c..e723b74 100644 --- a/R/processanimateR.R +++ b/R/processanimateR.R @@ -1,6 +1,7 @@ #' @title Animate cases on a process map #' -#' @description A function for creating a SVG animation of an event log on a process map created by \code{\link{process_map}} from the processmapR package. +#' @description A function for creating a SVG animation of an event log on a process map created by +#' \code{\link{process_map}} from the processmapR package. #' #' @param eventlog The event log object that should be animated #' @param processmap The process map created with processmapR that the event log should be animated on, diff --git a/inst/htmlwidgets/lib/modules/animation_slider.js b/inst/htmlwidgets/lib/modules/animation_slider.js deleted file mode 100644 index 9d5ed2b..0000000 --- a/inst/htmlwidgets/lib/modules/animation_slider.js +++ /dev/null @@ -1,179 +0,0 @@ -/* -processanimateR 1.0.0 -Copyright (c) 2018 Felix Mannhardt -Licensed under MIT license -*/ -function Slider(el) { - - var smargin = {top:5, right:20, bottom:0, left:50}; - var sheight = 75 - smargin.top - smargin.bottom; - - var slider = null, - sliderSvg = null, - sliderListener = null, - sliderLoop = null; - - this.getHeight = function() { - if (slider) { - return sheight + smargin.top + smargin.bottom; - } else { - return 0; - } - }; - - this.renderSlider = function(data, svg, width) { - - if (data.timeline && - // Polyfill fakesmile does not support pausing/unpausing for IE - typeof SVGSVGElement.prototype.animationsPaused === "function") { - - // Clean-up - if (sliderSvg) { - sliderSvg.remove(); - } - if (sliderListener) { - document.removeEventListener(sliderListener); - } - if (sliderLoop) { - window.cancelAnimationFrame(sliderLoop); - } - - // re-calculate dimensions - var swidth = width - smargin.left - smargin.right; - - if (data.mode === "relative") { - animMin = data.timeline_start; - animMax = data.timeline_end; - } else { - animMin = new Date(data.timeline_start); - animMax = new Date(data.timeline_end); - } - - slider = d3.sliderHorizontal() - .min(animMin) - .max(animMax) - .ticks(10) - .width(swidth) - .displayValue(true) - .on('onchange', function(val) { - svg.setCurrentTime((val - data.timeline_start) / data.factor); - }); - - if (data.mode === "relative") { - slider.tickFormat(function(val){ - return moment.duration(val, 'milliseconds').humanize(); - }); - slider.displayFormat(function(val){ - return moment.duration(val, 'milliseconds').humanize(); - }); - } else { - slider.displayFormat(d3.timeFormat("%x %X")); - } - - sliderSvg = d3.select(el).append("svg") - .attr("class", "processanimater-control") - .attr("width", swidth + smargin.left + smargin.right) - .attr("height", sheight + smargin.top + smargin.bottom); - - sliderSvg.append("g") - .attr("transform", "translate("+smargin.left+",30)") - .call(slider); - - var buttonsSvg = sliderSvg.append("g") - .attr("transform", "translate(0,15)"); - - // Inspired by https://gist.github.com/guilhermesimoes/fbe967d45ceeb350b765 - var play = "M11,10 L18,13.74 18,22.28 11,26 M18,13.74 L26,18 26,18 18,22.28", - pause = "M11,10 L17,10 17,26 11,26 M20,10 L26,10 26,26 20,26"; - - var controlButton = buttonsSvg - .append("g").attr("style", "pointer-events: bounding-box") - .append("path") - .attr("d", pause); - - var unpauseAnimation = function() { - svg.unpauseAnimations(); - controlButton - .transition() - .duration(500) - .attr("d", pause); - animateSlider(svg, data); - }; - - var pauseAnimation = function() { - svg.pauseAnimations(); - controlButton - .transition() - .duration(500) - .attr("d", play); - if (sliderLoop) { - window.cancelAnimationFrame(sliderLoop); - } - }; - - controlButton.on("click", function() { - if (svg.animationsPaused()) { - unpauseAnimation(); - } else { - pauseAnimation(); - } - }); - - sliderListener = document.addEventListener('keypress', function(event) { - - function getNumberFromKeyEvent(event) { - if (event.keyCode >= 96 && event.keyCode <= 105) { - return event.keyCode - 96; - } else if (event.keyCode >= 48 && event.keyCode <= 57) { - return event.keyCode - 48; - } - return null; - } - - if (svg.offsetParent !== null) { - if (event.keyCode === 32) { - if (svg.animationsPaused()) { - unpauseAnimation(); - } else { - pauseAnimation(); - } - } else { - var num = getNumberFromKeyEvent(event); - if (num !== null) { - svg.setCurrentTime((data.duration / 10) * num); - } - } - } - }); - - animateSlider(svg, data); - - // TODO the playback control should be factored out from the slider, but I leave this for later - if (data.initial_state === "paused") { - pauseAnimation(); - } - svg.setCurrentTime(Math.max(0,Math.min(data.duration, data.initial_time))); - } - }; - - function animateSlider(svg, data) { - if (data.mode === "relative") { - (function(){ - var time = svg.getCurrentTime(); - if (time > 0 && time <= data.duration) { - slider.silentValue(data.timeline_start + time * data.factor); - } - sliderLoop = window.requestAnimationFrame(arguments.callee); - })(); - } else { - (function(){ - var time = svg.getCurrentTime(); - if (time > 0 && time <= data.duration) { - slider.silentValue(new Date(data.timeline_start + (time * data.factor))); - } - sliderLoop = window.requestAnimationFrame(arguments.callee); - })(); - } - } - -} diff --git a/inst/htmlwidgets/processanimateR.js b/inst/htmlwidgets/processanimateR.js index 918e5c9..1ac3db9 100644 --- a/inst/htmlwidgets/processanimateR.js +++ b/inst/htmlwidgets/processanimateR.js @@ -10,33 +10,11 @@ HTMLWidgets.widget({ factory: function(el, width, height) { - var slider = new Slider(el); + var control = new PlaybackControl(el); var scales = new Scales(el); var tokens = null; var renderer = null; - var repeatLoop = null; - var repeatCount = 1; - - function repeatAnimation(data, svg) { - if (data.repeat_count === null) { - data.repeat_count = Infinity; - } - if (repeatLoop) { - window.cancelAnimationFrame(repeatLoop); - } - (function(){ - var time = svg.getCurrentTime(); - if (time > (data.duration + data.repeat_delay) && repeatCount < data.repeat_count) { - svg.setCurrentTime(0); - repeatCount++; - } - if (repeatCount < data.repeat_count) { - repeatLoop = window.requestAnimationFrame(arguments.callee); - } - })(); - } - return { renderValue: function(data) { @@ -63,12 +41,10 @@ HTMLWidgets.widget({ var tokenGroup = tokens.insertTokens(svg); tokens.attachEventListeners(svg, tokenGroup); - slider.renderSlider(data, svg, width); + control.renderPlaybackControl(data, svg, width); scales.renderLegend(data, svg, width, height); - repeatAnimation(data, svg); - - renderer.resize(width, Math.max(0, height - slider.getHeight())); + renderer.resize(width, Math.max(0, height - control.getHeight())); }); }, @@ -76,11 +52,15 @@ HTMLWidgets.widget({ resize: function(width, height) { if (renderer) { - slider.renderSlider(renderer.getData(), renderer.getSvg(), width); + control.renderPlaybackControl(renderer.getData(), renderer.getSvg(), width); scales.resizeLegend(renderer.getSvg(), width, height); - renderer.resize(width, Math.max(0, height - slider.getHeight())); + renderer.resize(width, Math.max(0, height - control.getHeight())); } + }, + + getPlaybackControl: function() { + return control; } }; diff --git a/inst/htmlwidgets/processanimateR.yaml b/inst/htmlwidgets/processanimateR.yaml index 512c375..9af6ad9 100644 --- a/inst/htmlwidgets/processanimateR.yaml +++ b/inst/htmlwidgets/processanimateR.yaml @@ -1,9 +1,9 @@ dependencies: - name: processanimater-libs - version: 0.3.0 + version: 1.0.0 src: "htmlwidgets/lib/modules" script: - - animation_slider.js + - animation_playback_control.js - animation_scales.js - animation_tokens.js - animation_renderer_graphviz.js diff --git a/vignettes/use-shiny-selections.Rmd b/vignettes/use-shiny-selections.Rmd index 89ceea7..2350a4e 100644 --- a/vignettes/use-shiny-selections.Rmd +++ b/vignettes/use-shiny-selections.Rmd @@ -36,7 +36,9 @@ shinyAnimation <- function(eventlog, min.time = 30, max.time = 600, default.time h4("Selected cases"), textOutput(ns("token_selection")), h4("Selected activities"), - textOutput(ns("activity_selection")) + textOutput(ns("activity_selection")), + h4("Current time"), + textOutput(ns("activity_time")) ) } @@ -58,6 +60,14 @@ shinyAnimation <- function(eventlog, min.time = 30, max.time = 600, default.time paste0("(", activities$id, ",", activities$activity, ")", collapse = ",") } }) + + output$activity_time <- renderText({ + if (is.null(input$process_time)) { + "0" + } else { + input$process_time + } + }) output$process <- renderProcessanimater(expr = { animate_process(eventlog, ...) @@ -72,7 +82,7 @@ shinyAnimation <- function(eventlog, min.time = 30, max.time = 600, default.time # Two animations server <- function(input, output, session) { - callModule(animation, "module1", animation_mode = "relative") + callModule(animation, "module1", mode = "relative") callModule(animation, "module2") }