R shinyHeatmap: How to Monitor User Sessions in R Shiny for Free

Reading time:
time
min
By:
Dario Radečić
August 4, 2022

User session monitoring through heatmaps is huge. It allows you to see what works and what doesn't for your R Shiny app, and generally how users interact with it. Also, it helps you and your organization build a user adoption strategy with user behavior analytics. So, <b>how can you get started for free?</b> The answer is simple - with the R <code>shinyHeatmap</code> package. We've previously explored the R Shiny Hotjar option for monitoring user behavior. But this option leaves empty heatmaps for some. Why? Bugs that are notoriously difficult to track and resolve. Also, Hotjar is a freemium service, which means you'll have to pay as soon as you exceed 35 sessions per day (July 2022). R <code>shinyHeatmap</code> is different - it's easier to get started with and is completely free of charge. <blockquote>Can your R Shiny apps be used by all users? <a href="https://appsilon.com/r-shiny-accessibility/" target="_blank" rel="noopener">It might be time to think about accessability</a>.</blockquote> Table of contents: <ul><li><a href="#introduction">What is shinyHeatmap?</a></li><li><a href="#comparison">shinyHeatmap or Hotjar - How to Choose?</a></li><li><a href="#get-started">How to Get Started with shinyHeatmap in R Shiny Apps</a></li><li><a href="#output">Heatmap Output - Walkthrough and Customization</a></li><li><a href="#summary">Summary of shinyHeatmap</a></li></ul> <hr /> <h2 id="introduction">What is R shinyHeatmap?</h2> The <a href="https://github.com/RinteRface/shinyHeatmap" target="_blank" rel="noopener">shinyHeatmap</a> R package aims to provide a free and local alternative to more advanced user session monitoring platforms, such as <a href="https://www.hotjar.com/" target="_blank" rel="noopener">Hotjar</a>. It provides just enough features to let you know how users use your Shiny dashboards. As of writing this post, <code>shinyHeatmap</code> isn't available on CRAN. To install it, you'll first have to install the <code>devtools</code> package through the R console: <pre><code class="language-r">install.packages("devtools")</code></pre> Once installed, pull and install the <code>shinyHeatmap</code> package through GitHub: <pre><code class="language-r">devtools::install_github("RinteRface/shinyHeatmap")</code></pre> That's actually all you need to get started. We'll cover the hands-on part in a minute, but first, let's discuss when <code>shinyHeatmap</code> should be the tool of your choice, and when should you consider more advanced alternatives. <h2 id="comparison">R shinyHeatmap or Hotjar - How to Choose?</h2> <h3>Hotjar drawbacks for Shiny</h3> If you want a tool that looks good on paper and don't care about cost - look no further than <a href="https://www.hotjar.com/" target="_blank" rel="noopener">Hotjar</a>. However, Hotjar can be more difficult to set up. You must have your R Shiny app deployed, which isn't handy if you're just starting out. <blockquote>New to R Shiny app deployment? <a href="https://appsilon.com/how-to-share-r-shiny-apps/" target="_blank" rel="noopener">Here are top 3 methods you must know</a>.</blockquote> Further, you have to register an account and embed a tracking code in your app. After doing so, you have to redeploy the app. All in all, it's not too complicated, but there are some bugs. <h4>Missing heatmaps</h4> Hotjar sessions take some time to appear in your dashboard - if they appear at all. We at Appsilon and many others have found Hotjar to be buggy depending on the framework you're using. Many users have reported that sessions aren't displayed in the dashboards, and that's a deal-breaking issue. For example, if you're exploring options for Shiny for Python - some web frameworks are altogether <a href="https://help.hotjar.com/hc/en-us/articles/115012499507-Platforms-and-Frameworks-not-Compatible-with-Hotjar" target="_blank" rel="noopener">not compatible with Hotjar</a> like Electron. <h4>Cost</h4> Also, we have to discuss pricing. If you're an indie developer, paying $31 a month when billed annually is just too expensive. It's a negligible cost for a full-scale organization, but still, that's the most basic premium plan allowing you to record 100 sessions per day. <b>Moral of the story:</b> Hotjar is amazing if you can make it work and if you can afford it - <i>if</i> being the crucial part. <h3>shinyHeatmap - heatmaps made for R Shiny apps</h3> The R <code>shinyHeatmap</code> package is different. It only requires a <code>www</code> folder for saving logs, and what it collects is barebones. The logs are collected in JSON format where each interaction is a JSON object containing X and Y coordinates of an event. Because of this, you can rest assured that there won't be any data privacy concerns. No actual user data is collected, only the coordinates of their clicks with the purpose of aggregation and visual interpretation. This package is free and open-source. There are no fancy features such as events and identify APIs that come with a premium Hotjar plan, but that's okay for most users. If you're only interested in heatmaps, <code>shinyHeatmap</code> is the way to go. Long story short: <ul><li>Use <code>shinyHeatmap</code> if you want barebones logs and to ensure you receive good heatmap visualizations, free of charge</li></ul> Next, let's see how you can get started configuring the <code>shinyHeatmap</code> package. <h2 id="get-started">How to Get Started with shinyHeatmap in R Shiny Apps</h2> You already have <code>shinyHeatmap</code> installed, so now let's begin with the fun part. For the Shiny dashboard of choice, we'll reuse the clustering app from our <a href="https://appsilon.com/monitoring-r-shiny-user-adoption/" target="_blank" rel="noopener">Tools for Monitoring User Adoption</a> article. Here's the source code: <pre><code class="language-r">library(shiny) <br>ui &lt;- fluidPage(  headerPanel("Iris k-means clustering"),    sidebarLayout(      sidebarPanel(        selectInput(            inputId = "xcol",            label = "X Variable",            choices = names(iris)        ),        selectInput(            inputId = "ycol",            label = "Y Variable",            choices = names(iris),            selected = names(iris)[[2]]        ),        numericInput(            inputId = "clusters",            label = "Cluster count",            value = 3,            min = 1,            max = 9        )      ),    mainPanel(      plotOutput("plot1")    )  ) ) <br>server &lt;- function(input, output, session) {  selectedData &lt;- reactive({    iris[, c(input$xcol, input$ycol)]  })  clusters &lt;- reactive({    kmeans(selectedData(), input$clusters)  })  output$plot1 &lt;- renderPlot({    palette(c(      "#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",      "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999"    )) <br>    par(mar = c(5.1, 4.1, 0, 1))    plot(selectedData(),        col = clusters()$cluster,        pch = 20, cex = 3    )    points(clusters()$centers, pch = 4, cex = 4, lwd = 4)  }) } <br>shinyApp(ui = ui, server = server)</code></pre> And here's what the app looks like: <img class="size-full wp-image-14777" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a0fb3d70e0efa07ee9_2c637196_1-3.webp" alt="Image 1 - Clustering R Shiny application" width="2336" height="1746" /> Image 1 - Clustering R Shiny application Including <code>shinyHeatmap</code> is a two-step process: <ol><li><code>ui()</code> - wrap <code>fluidPage()</code> with a call to <code>with_heatmap()</code></li><li><code>server()</code> - add a call to <code>record_heatmap()</code> to the top.</li></ol> If you want to copy and paste, here's the code: <pre><code class="language-r">library(shiny) library(shinyHeatmap) <br>ui &lt;- with_heatmap(  fluidPage(    headerPanel("Iris k-means clustering"),    sidebarLayout(      sidebarPanel(        selectInput(          inputId = "xcol",          label = "X Variable",          choices = names(iris)        ),        selectInput(          inputId = "ycol",          label = "Y Variable",          choices = names(iris),          selected = names(iris)[[2]]        ),        numericInput(          inputId = "clusters",          label = "Cluster count",          value = 3,          min = 1,          max = 9        )      ),      mainPanel(        plotOutput("plot1")      )    )  ) ) <br>server &lt;- function(input, output, session) {  record_heatmap()    selectedData &lt;- reactive({    iris[, c(input$xcol, input$ycol)]  })  clusters &lt;- reactive({    kmeans(selectedData(), input$clusters)  })  output$plot1 &lt;- renderPlot({    palette(c(      "#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",      "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999"    )) <br>    par(mar = c(5.1, 4.1, 0, 1))    plot(selectedData(),      col = clusters()$cluster,      pch = 20, cex = 3    )    points(clusters()$centers, pch = 4, cex = 4, lwd = 4)  }) } <br>shinyApp(ui = ui, server = server) </code></pre> Once launched, the Shiny dashboard doesn't look any different from before. We've played around with the inputs to make the image somewhat different: <img class="size-full wp-image-14779" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a17d51e4abfae76d81_5ff4c7d7_2-3.webp" alt="Image 2 - Clustering dashboard after adding shinyHeatmap calls" width="2360" height="1690" /> Image 2 - Clustering dashboard after adding shinyHeatmap calls Click around the dashboard a couple of times. Display different variables on X and Y axes, and tweak the number of clusters. Everything you do will get saved to the <code>www</code> folder. To be more precise, the events are split by minute, with each minute represented by a single JSON file: <img class="size-full wp-image-14781" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a1b9bf482cf5d0211b_a89db31d_3-2.webp" alt="Image 3 - Contents of the www directory" width="1868" height="1096" /> Image 3 - Contents of the www directory Once opened, a single JSON file looks like this: <img class="size-full wp-image-14783" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a2842ddef80cb72d37_b5961587_4-3.webp" alt="Image 4 - Contents of a single JSON log file" width="1160" height="1108" /> Image 4 - Contents of a single JSON log file But how can you use these logs to visualize the usage heatmap? That's what we'll discuss in the following section. <h2 id="output">Heatmap Output - Walkthrough and Customization</h2> To render a heatmap over the dashboard, you'll have to replace <code>record_heatmap()</code> with <code>download_heatmap()</code> in a call to <code>server()</code>. We can tweak the output by changing the parameters, but more on that later. <b>Keep in mind:</b> Because you've removed a call to <code>record_heatmap()</code>, new events aren't recorded. Anyhow, here's the code for the updated <code>server()</code> function: <pre><code class="language-r">server &lt;- function(input, output, session) {  download_heatmap()    selectedData &lt;- reactive({    iris[, c(input$xcol, input$ycol)]  })  clusters &lt;- reactive({    kmeans(selectedData(), input$clusters)  })  output$plot1 &lt;- renderPlot({    palette(c(      "#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",      "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999"    )) <br>    par(mar = c(5.1, 4.1, 0, 1))    plot(selectedData(),      col = clusters()$cluster,      pch = 20, cex = 3    )    points(clusters()$centers, pch = 4, cex = 4, lwd = 4)  }) }</code></pre> <img class="size-full wp-image-14785" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a27d51e4abfae76f95_5d23e86b_5-3.webp" alt="Image 5 - Heatmap overlaying the R Shiny app" width="2360" height="1690" /> Image 5 - Heatmap overlaying the R Shiny app Neat, isn't it? You can also click on the <b>Heatmap</b> button to show events in selected time intervals. By default, all logs are aggregated and shown, but you can show a heatmap for a time range only: <img class="size-full wp-image-14787" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a3a11449a24ce9b5d1_aa40e536_6.gif" alt="Image 6 - Playing around with the Heatmap UI" width="1066" height="702" /> Image 6 - Playing around with the Heatmap UI In case you don't want the <b>Heatmap</b> button, you can set <code>show_ui = FALSE</code> in a call to <code>download_heatmap()</code>. By doing so, the <b>heatmap image will be downloaded</b>: <pre><code class="language-r">download_heatmap(show_ui = FALSE)</code></pre> <img class="size-full wp-image-14789" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a4876df5e188dc69f3_235013dd_7-2.webp" alt="Image 7 - Downloading heatmap as an image" width="1680" height="1024" /> Image 7 - Downloading the heatmap as an image The image includes events from all logs, and here's what it looks like on our end: <img class="size-full wp-image-14791" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a4b9bf482cf5d02281_0e048526_8-2.webp" alt="Image 8 - Downloaded heatmap image" width="971" height="470" /> Image 8 - Downloaded heatmap image Is that it? Well, no. You can also modify how the heatmap looks by changing a couple of function parameters. <h3>How to customize the looks of shinyHeatmap</h3> The <code>download_heatmap()</code> function accepts an <code>options</code> parameter. It is a list in which you can tweak the size, opacity, blur, and color of your heatmaps. Here's an example - we'll slightly increase the size and change the color gradient altogether: <pre><code class="language-r">download_heatmap(  options = list(    radius = 20,    maxOpacity = 0.8,    minOpacity = 0,    blur = 0.75,    gradient = list(        ".5" = "green",        ".8" = "red",        ".95" = "black"    )  )   )</code></pre> Here's what the heatmap looks like: <img class="size-full wp-image-14793" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3a5c51a64697db034fa_ecfceaf6_9-1.webp" alt="Image 9 - Heatmap with updated visuals" width="2374" height="1688" /> Image 9 - Heatmap with updated visuals And that's how you can install, configure, use, and customize R <code>shinyHeatmap</code> package in R Shiny. Let's make a short recap next before you get started with shinyHeatmap for your user testing. <hr /> <h2 id="summary">Summary of R shinyHeatmap</h2> Monitoring user behavior used to be hard but nowadays it boils down to adding a couple of lines of code. There's no excuse to not inspect how people use your R Shiny dashboards, especially since <code>shinyHeatmap</code> is completely free! <blockquote>Looking to conduct effective user tests? See <a href="https://appsilon.com/user-tests-build-better-shiny-apps-with-effective-user-testing/" target="_blank" rel="noopener">how Appsilon conducts user tests for Shiny dashboards</a>.</blockquote> Are you an avid user of R <code>shinyHeatmap</code> package? Or do you prefer some other alternative? Please let us know in the comment section below. Also, don't hesitate to hit us on Twitter - <a href="https://twitter.com/appsilon" target="_blank" rel="noopener">@appsilon</a> - we like discussing R and anything data science related. <blockquote>Considering a carreer as an R Shiny developer? <a href="https://appsilon.com/how-to-start-a-career-as-an-r-shiny-developer/" target="_blank" rel="noopener">Take a look at our guide for complete beginners</a>.</blockquote>

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!

Is Your Software GxP Compliant?

Download a checklist designed for clinical managers in data departments to make sure that software meets requirements for FDA and EMA submissions.
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
shiny
tutorials
shiny dashboards