diff --git a/_freeze/posts/chartJS/index/execute-results/html.json b/_freeze/posts/chartJS/index/execute-results/html.json new file mode 100644 index 0000000..369734c --- /dev/null +++ b/_freeze/posts/chartJS/index/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "c8576ad6e874a001075c7ec73ba7500a", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Boost your shiny app with sparkling data visualizations: A deep dive into Chart.js JavaScript library\"\nauthor: \"Yohann Mansiaux\"\ndate: \"2024-04-30\"\ncategories: [shiny, javascript]\nimage: \"image.jpg\"\n---\n\n\n\n**Let's continue our exploration of integrating JavaScript code into a {shiny} application! We will show how to move beyond the classic graphs produced in base R or with {ggplot2} to explore the interactive dataviz production libraries of JavaScript, particularly the Chart.js library.**\n\nIf you missed my first article on integrating JavaScript libraries into a {shiny} application I invite you to read it before diving into this one.\n\nCrucial concepts are covered and will not be repeated here. We particularly think about:\n\n- How to add a JavaScript library's dependencies to a {shiny} application \n- How to call JavaScript code from R\n\n## TL;DR\n\n- Creating interactive charts that go beyond the usual dataviz produced in R is possible by integrating a JavaScript library!\n- \n - We use the example of Chart.js, a very popular JavaScript dataviz library\n - Specificities related to integrating Chart.js into a `{shiny}` application are addressed, including passing data from R to JavaScript and the differences in expected data formats.\n - We'll see how to make sure our JavaScript code is working properly by using the web browser's console.\n\n## Importing Chart.js into a `{shiny}` app created with `{golem}`\n\n- Chart.js is a JavaScript library that allows you to create many types of charts (bars, lines, radar, etc.) and customize them as you wish\n- It is very well documented\n- It is the most popular JavaScript dataviz library on GitHub (over 60,000 \"stars\" at the time of this article's publication)\n\nTo get an overview of the possibilities offered by Chart.js, visit the official page: https://www.chartjs.org/docs/latest/samples/information.html\n\n### Add the dependencies to Chart.js in your {shiny} app\n\n**The following sections assume that you have already created a `{shiny}` app with `{golem}`.**\n\nIf this is not the case and you want to learn more about `{golem}`, I invite you to consult the official documentation.\n\nTo add Chart.js to your `{shiny}` app, you will need to find a way to incorporate the necessary files for its operation into your application. As we saw in our previous article, two solutions are possible.\n\n- Use a \"CDN\" (Content Delivery Network) to load the files from a third-party server.\n- Download the necessary files and integrate them directly into the application.\n\nWe will use the \"CDN\" method here.\n\nGo to the \"Getting Started\" section of Chart.js documentation .\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](img/01-cdn.png){width=80%}\n:::\n:::\n\n\n\nWe retrieve the CDN URL and store this information for later use.\n\nAfter creating the skeleton of an application via `{golem}`, we will add the Chart.js dependency.\n\nLet's open the `R/app_ui.R` file of our application and add the link we copied earlier into the body of the `golem_add_external_resources()` function.\n\n``` r\ngolem_add_external_resources <- function() {\n add_resource_path(\n \"www\",\n app_sys(\"app/www\")\n )\n \n tags$head(\n favicon(),\n bundle_resources(\n path = app_sys(\"app/www\"),\n app_title = \"chartJS\"\n ),\n # Add here other external resources\n # for example, you can add shinyalert::useShinyalert()\n # Chart.js\n tags$script(src = \"https://cdn.jsdelivr.net/npm/chart.js\")\n )\n}\n```\n\n### How to know if Chart.js is properly imported ?\n\nThe \"Getting Started\" section previously consulted to retrieve the CDN link indicates that it is necessary to incorporate the HTML `` tag into our application to display a Chart.js chart. We add this element to the `R/app_ui.R` file of our application.\n\n\n``` r\napp_ui <- function(request) {\n tagList(\n # Leave this function for adding external resources\n golem_add_external_resources(),\n # Your application UI logic\n fluidPage(\n h1(\"golemchartjs\"),\n tags$div(\n tags$canvas(id=\"myChart\")\n )\n )\n )\n}\n```\n\nTo verify that Chart.js is properly imported into our application, we run our app with `golem::run_dev()`, and the rest will take place in the web browser.\n\n**NB: The following screenshots were taken using the Google Chrome browser.**\n\nIn the window of our application, right-click and then select \"Inspect\". In the new window that opens, choose the \"Console\" tab and type the command to generate a Chart.js chart, as indicated once again in the \"Getting Started\" section between the HTML `script` tags.\n\nThe code to copy and paste into the console is the following:\n\n``` javascript\n const ctx = document.getElementById('myChart');\n\n new Chart(ctx, {\n type: 'bar',\n data: {\n labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n datasets: [{\n label: '# of Votes',\n data: [12, 19, 3, 5, 2, 3],\n borderWidth: 1\n }]\n },\n options: {\n scales: {\n y: {\n beginAtZero: true\n }\n }\n }\n });\n```\n\n\n\nThe chart from the demo page appears as expected! ๐ŸŽ‰ \n\nWe can move on! ๐Ÿ˜Š \n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step1\n\n## Creating a Bar Chart with Chart.js\n\nThe code used previously allowed us to verify that Chart.js was properly imported into our application. Now, we will see how to create a Chart.js chart from our `{shiny}` application. The goal is to produce bar charts for various datasets with customizable options based on user choices.\n\nLet's revisit the code executed previously:\n\n``` javascript\n const ctx = document.getElementById('myChart');\n\n new Chart(ctx, {\n type: 'bar',\n data: {\n labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n datasets: [{\n label: '# of Votes',\n data: [12, 19, 3, 5, 2, 3],\n borderWidth: 1\n }]\n },\n options: {\n scales: {\n y: {\n beginAtZero: true\n }\n }\n }\n });\n```\n\nWe could imagine passing the `labels`, `label`, `data`, and `borderWidth` elements as function parameters.\n\nLet's go! ๐Ÿš€\n\n### Creating a JavaScript code usable from R\n\nWe saw in our previous article that the way to call JavaScript code from R is to use a \"JS handler\". To do this, go to the `dev/02_dev.R` file! We add the following line in the \"External Resources\" section:\n\n``` r\ngolem::add_js_handler(\"barchartJS\")\n```\n\nWe fill in the skeleton by indicating \"barchartJS\" as the name of our handler and adding the JavaScript code we saw previously.\n\n``` javascript\n$(document).ready(function () {\n Shiny.addCustomMessageHandler(\"barchartJS\", function (arg) {\n const ctx = document.getElementById(\"myChart\");\n\n new Chart(ctx, {\n type: \"bar\",\n data: {\n labels: [\"Red\", \"Blue\", \"Yellow\", \"Green\", \"Purple\", \"Orange\"],\n datasets: [\n {\n label: \"# of Votes\",\n data: [12, 19, 3, 5, 2, 3],\n borderWidth: 1,\n },\n ],\n },\n options: {\n scales: {\n y: {\n beginAtZero: true,\n },\n },\n },\n });\n });\n});\n\n```\n\nWe replace the `labels`, `label`, `data`, and `borderWidth` parameters, which are hardcoded here, with the future elements passed as arguments to our handler. The notation to use here will be `arg.param_name` to access the values passed by our `{shiny}` application. The `.` notation is a JavaScript convention for accessing properties of an object. To draw a parallel with R, it's somewhat like using `arg$param_name`.\n\nAt the beginning of our handler, we add a call to the `console.log()` function to check the contents of the `arg` element from the JS console. This will allow us to verify that the elements passed from R are correctly transmitted to our handler.\n\n``` javascript\n$( document ).ready(function() {\n Shiny.addCustomMessageHandler('barchartJS', function(arg) {\n console.log(arg); \n const ctx = document.getElementById('myChart');\n\n new Chart(ctx, {\n type: 'bar',\n data: {\n labels: arg.labels,\n datasets: [{\n label: arg.label,\n data: arg.data,\n borderWidth: arg.borderWidth\n }]\n },\n options: {\n scales: {\n y: {\n beginAtZero: true\n }\n }\n }\n }); \n })\n});\n```\nWe will add elements to the `R/app_ui.R` file to generate the parameters to be passed to our handler:\n\n- `arg.labels` will be a vector of 5 character strings, randomly chosen from the letters of the latin alphabet.\n- `arg.label` will be a character string, randomly chosen from the letters of the latin alphabet.\n- `arg.data` will be a vector of 5 integer numbers, randomly chosen between 1 and 100.\n- `arg.borderWidth` will be an integer, randomly chosen between 1 and 5.\n\nThe display of the chart will be triggered by clicking a \"Show Barplot\" button.\n\nHere is the content of our `R/app_ui.R` file:\n\n``` r\napp_ui <- function(request) {\n\ttagList(\n\t\t# Leave this function for adding external resources\n\t\tgolem_add_external_resources(),\n\t\t# Your application UI logic\n\t\tfluidPage(\n\t\t\th1(\"golemchartjs\"),\n\t\t\tactionButton(\n\t\t\t\tinputId = \"showbarplot\",\n\t\t\t\tlabel = \"Show Barplot\"\n\t\t\t),\n\t\t\ttags$div(\n\t\t\t\ttags$canvas(id = \"myChart\")\n\t\t\t)\n\t\t)\n\t)\n}\n```\n\nAnd the content of the `R/app_server.R` file :\n\n``` r\napp_server <- function(input, output, session) {\n\tobserveEvent(input$showbarplot, {\n\t\tapp_labels <- sample(letters, 5)\n\t\tapp_label <- paste0(sample(letters, 10), collapse = \"\")\n\t\tapp_data <- sample(1:100, 5)\n\t\tapp_borderWidth <- sample(1:5, 1)\n\n\t\tgolem::invoke_js(\n\t\t\t\"barchartJS\",\n\t\t\tlist(\n\t\t\t\tlabels = app_labels,\n\t\t\t\tlabel = app_label,\n\t\t\t\tdata = app_data,\n\t\t\t\tborderWidth = app_borderWidth\n\t\t\t)\n\t\t)\n\t})\n}\n```\n\nHere are the key points to remember:\n\n- The first parameter in the call to `golem::invoke_js()` is the name of the JavaScript handler.\n- The following parameters are the elements to be passed as arguments to our handler. They should be passed in a named list where the names correspond to the elements in the `arg` object of our handler.\n\nLet's run our application with `golem::run_dev()` and verify that everything works as expected!\n\n\n\nCongratulations! ๐Ÿ‘ \n\nIn addition to the displayed chart, we can see that the JavaScript console in the browser correctly shows the content of the `arg` object, including its 4 sub-elements: `labels`, `label`, `data`, and `borderWidth`.\n\nAnd if you click the button again, what happens?\n\n\n\nThe chart does not update; it remains stuck on the first chart! ๐Ÿ˜ฎ\n\nThe JavaScript console indicates that the `arg` object has indeed been updated, but the chart does not refresh. Additionally, an error message appears in the JavaScript console: \"Error: Canvas is already in use. Chart with ID '0' must be destroyed before the canvas with ID 'myChart' can be reused.\"\n\nLet's try to understand what's happening: in the `R/app_ui.R` file, we added a `canvas` element with the ID \"myChart\" (with `tags$canvas(id = \"myChart\")`). This element is used to display the chart. When we click the \"Show Barplot\" button, a new chart is generated and displayed in this element. However, the previous chart is not destroyed, and the error message indicates that the \"canvas\" is already in use.\n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step2\n\n### Why isn't the chart updating?\n\nTo find the answer, we need to refer back to the Chart.js documentation. We can read in the \".destroy()\" section that in order to reuse the HTML \"canvas\" element for displaying a new chart, it is necessary to destroy the previous chart.\n\nThere is also a command \".update()\" for updating an existing chart. This method seems more appropriate here, as we are using the same type of chart, with only a few parameters changing. The `.update()` method allows updating an existing chart without having to destroy and recreate it, which will be less \"brutal\" visually (with a chart disappearing and then reappearing). However, the `.destroy()` method should be kept in mind for cases where we want to radically change the type of chart, for example.\n\nUpdating a chart implies that a chart has already been generated once. Therefore, we need to modify our JavaScript handler to account for this and find a way to detect the existence of a chart on our page. For this, we will refer again to the Chart.js documentation, particularly the `getChart` method: https://www.chartjs.org/docs/latest/developers/api.html#static-getchart-key.\n\nThe command to use is in the following form: `const chart = Chart.getChart(\"canvas-id\");`. According to the documentation, if the chart exists, the variable `chart` will contain the Chart.js object associated with the HTML \"canvas\" element. If the chart does not exist, the variable `chart` will be `undefined`.\n\nFor this command to work, we need to replace \"canvas-id\" with the ID of our \"canvas\", which is \"myChart\" here: `const chart = Chart.getChart(\"myChart\");`\n\nLet's restart our application. We indeed find that the `chart` object is `undefined` as long as the chart has not been created, and it correctly reflects this status afterwards.\n\n\n\nWe can adapt our code as follows:\n\n- If `chart` is `undefined`, we create a new chart.\n \n- If `chart` is not `undefined`, we update the existing chart.\n\nWe adapt our handler by referring to the documentation for the `.update()` method: https://www.chartjs.org/docs/latest/developers/api.html#update-mode\n\n``` javascript\n$(document).ready(function () {\n Shiny.addCustomMessageHandler(\"barchartJS\", function (arg) {\n console.log(arg);\n const ctx = document.getElementById(\"myChart\");\n\n const chart = Chart.getChart(\"myChart\");\n\n if (chart == undefined) {\n console.log(\"Creating a new chart\");\n new Chart(ctx, {\n type: \"bar\",\n data: {\n labels: arg.labels,\n datasets: [\n {\n label: arg.label,\n data: arg.data,\n borderWidth: arg.borderWidth,\n },\n ],\n },\n options: {\n scales: {\n y: {\n beginAtZero: true,\n },\n },\n },\n });\n } else {\n console.log(\"Updating an existing chart\");\n chart.data.labels = arg.labels;\n chart.data.datasets[0].label = arg.label;\n chart.data.datasets[0].data = arg.data;\n chart.data.datasets[0].borderWidth = arg.borderWidth;\n chart.update();\n }\n });\n});\n\n```\n\nThis example is a bit more complex than those seen so far:\n\n- Retrieve the Chart.js object associated with the HTML \"canvas\" element using the method `Chart.getChart(\"myChart\")`.\n\n- Check if this object is `undefined`: if it is, use the code that has been working until now to create a new chart.\n\n- If it is not `undefined`, overwrite the configuration elements you want to update and then use the `.update()` method. Note the specifics of handling configuration elements: `chart.data.labels = arg.labels` for the labels, `chart.data.datasets[0].label = arg.label` for the label, etc. Use `.` to access object properties, with each `.` allowing access to a deeper level of \"depth\". It is also important to note that array indexing starts at 0 in JavaScript, not at 1 like in R.\n\nAfter all these efforts, let's see if everything is back in order ๐Ÿ˜„!\n\n\n\nPhew, everything is OK this time! ๐Ÿฅฒ\n\nWe've touched on a more complex case of using a JavaScript library in a `{shiny}` application. It is crucial to understand the library's functioning by delving into the depths of its documentation. Moreover, one of the advantages of using a very popular library is that you can often find help on StackOverflow ๐Ÿ˜Š (here is an example of using the `.destroy()` method).\n\nFeel free to go further in customizing your chart, such as changing the bar colors: https://www.chartjs.org/docs/latest/general/colors.html and https://www.chartjs.org/docs/latest/charts/bar.html. \n\nThe best way to learn is to try reproducing examples from the documentation.\n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step3\n\n## Creating a Scatter Plot with Chart.js\n\nWe will attempt to create a scatter plot with Chart.js. To develop our code, we will rely on the Chart.js documentation: https://www.chartjs.org/docs/latest/charts/scatter.html.\n\nAs before, our code will be stored in a JS handler. Therefore, we will add a new handler in the `dev/02_dev.R` file:\n\n``` r\ngolem::add_js_handler(\"scatterplotJS\")\n```\n\nThe documentation is slightly different from that provided for bar charts. We will need to adapt our handler accordingly. We identify an element `config`, which will include the `type`, `data`, and `options` elements we have already seen. There is also a `data` element containing `datasets` and `labels`.\n\nWe will fill in the skeleton of our handler with the JavaScript code from the Chart.js documentation. Initially, we will leave out the \"update\" part.\n\n``` javascript\n$(document).ready(function () {\n Shiny.addCustomMessageHandler(\"scatterplotJS\", function (arg) {\n const ctx = document.getElementById(\"myChart2\");\n\n const data = {\n datasets: [\n {\n label: \"Scatter Dataset\",\n data: [\n {\n x: -10,\n y: 0,\n },\n {\n x: 0,\n y: 10,\n },\n {\n x: 10,\n y: 5,\n },\n {\n x: 0.5,\n y: 5.5,\n },\n ],\n backgroundColor: \"rgb(255, 99, 132)\",\n },\n ],\n };\n\n const config = {\n type: \"scatter\",\n data: data,\n options: {\n scales: {\n x: {\n type: \"linear\",\n position: \"bottom\",\n },\n },\n },\n };\n new Chart(ctx, config);\n });\n});\n\n```\n\nOur JS handler \"scatterplotJS\" is ready! We need to add the \"div\" and \"canvas\" to the UI to display the generated chart. We need to modify the HTML ID of our \"canvas\" to avoid any conflict with the bar chart. It will be named \"myChart2\" here.\n\nNote that there is a slightly different syntax compared to the code used for the bar chart, where the call to \"new Chart\" was made directly with the `data` and `options` elements. Here, we store these elements in `data` and `config` variables before passing them to `new Chart`.\n\nNext, we add the following to the `R/app_ui.R` file:\n\n``` r\nh1(\"Scatterplot\"),\nactionButton(\n\tinputId = \"showscatterplot\",\n\tlabel = \"Show Scatterplot\"\n),\ntags$div(\n\ttags$canvas(id = \"myChart2\")\n)\n```\n\nWe add the following to the `R/app_server.R` file:\n\n``` r\n observeEvent(input$showscatterplot, {\n golem::invoke_js(\n \"scatterplotJS\",\n list(\n )\n )\n })\n```\n\nOur handler does not use any elements passed from R. However, it is necessary to pass an empty list as an argument to ensure the proper functioning of `golem::invoke_js()`.\n\nLet's run your application with `golem::run_dev()` and verify that everything works as expected!\n\n\n\nThe chart from the documentation works! ๐ŸŽ‰\n\nNow, let's go further by passing our own data as input.\n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step4\n\n### An example with the iris dataset\n\nWe will use the `iris` dataset to generate a scatter plot. We will pass as arguments to our JS handler the data from the `Sepal.Length` and `Sepal.Width` columns.\n\nAs with the bar chart, we will use elements passed from R through the `arg` object in JavaScript.\n\nWe modify the `data` object to include a legend title and, most importantly, the data. To observe the elements passed from R, we add a call to `console.log()`.\n\n``` javascript\nconsole.log(arg);\nconst data = {\n datasets: [\n {\n label: arg.label,\n data: arg.data,\n backgroundColor: \"rgb(255, 99, 132)\",\n },\n ],\n};\n```\t\n\nAs a reminder, in the example from the documentation, the data is passed in the form of an \"array of dictionaries\". Each dictionary contains the keys `x` and `y` for the point coordinates.\n\n``` javascript\ndata: [{\n x: -10,\n y: 0\n }, {\n x: 0,\n y: 10\n }, {\n x: 10,\n y: 5\n }, {\n x: 0.5,\n y: 5.5\n}]\n```\n\nLet's try to pass the contents of the `Sepal.Length` and `Sepal.Width` columns via a list. We make the following modification in `R/app_server.R`:\n\n``` r\nobserveEvent(input$showscatterplot, {\n\tgolem::invoke_js(\n\t\t\"scatterplotJS\",\n\t\tlist(\n\t\t\tlabel = \"My scatterplot\",\n\t\t\tdata = list(\n\t\t\t\tx = iris$Sepal.Length,\n\t\t\t\ty = iris$Sepal.Width\n\t\t\t)\n\t\t)\n\t)\n})\n```\n\nWe restart our application, and unfortunately, nothing shows up!\n\n\n\nThanks to the `console.log()` call in our handler, we can observe the content of the `arg` object in the JavaScript console of the browser. We notice that the data passed is not in the correct format. Here, we get an array of two elements, the first containing the values of `Sepal.Length` and the second containing the values of `Sepal.Width`, which is not the expected format.\n\nHere, we need to do some work on the R side to transform our data into the expected format.\n\nIf we display a JSON preview of the data we passed as input, indeed the rendering is incorrect.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\njsonlite::toJSON(\n\tlist(\n\t\tx = iris$Sepal.Length,\n\t\ty = iris$Sepal.Width\n\t)\n)\n```\n:::\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n\n```\n{\"x\":[5.1,4.9,4.7,4.6,5,5.4,4.6,5,4.4,4.9],\"y\":[3.5,3,3.2,3.1,3.6,3.9,3.4,3.4,2.9,3.1]} \n```\n\n\n:::\n:::\n\n\n\nFor manipulating lists, the `{purrr}` package is a top choice.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew_data <- purrr::transpose(\n\tlist(\n\t\tx = iris$Sepal.Length,\n\t\ty = iris$Sepal.Width\n\t)\n)\njsonlite::toJSON(\n\tnew_data,\n\tauto_unbox = TRUE\n)\n```\n:::\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n\n```\n[{\"x\":5.1,\"y\":3.5},{\"x\":4.9,\"y\":3},{\"x\":4.7,\"y\":3.2},{\"x\":4.6,\"y\":3.1},{\"x\":5,\"y\":3.6},{\"x\":5.4,\"y\":3.9},{\"x\":4.6,\"y\":3.4},{\"x\":5,\"y\":3.4},{\"x\":4.4,\"y\":2.9},{\"x\":4.9,\"y\":3.1}] \n```\n\n\n:::\n:::\n\n\n\nThe rendering seems to be more in line with what is expected by Chart.js. Therefore, we will modify our code to pass the data in this manner.\n\n``` r\nobserveEvent(input$showscatterplot, {\n\tgolem::invoke_js(\n\t\t\"scatterplotJS\",\n\t\tlist(\n\t\t\tlabel = \"My scatterplot\",\n\t\t\tdata = purrr::transpose(\n\t\t\t\tlist(\n\t\t\t\t\tx = iris$Sepal.Length,\n\t\t\t\t\ty = iris$Sepal.Width\n\t\t\t\t)\n\t\t\t)\n\t\t)\n\t)\n})\n```\n\nLet's observe the result:\n\n\n\nThis time it's good! ๐Ÿ˜Š We can see in the JavaScript console that the data has indeed been passed in the correct format.\n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step5\n\n### A Little Extra Polish\n\nOur chart still lacks titles for the axes! To find out how to do this, the documentation comes to our rescue once again: https://www.chartjs.org/docs/latest/axes/labelling.html#scale-title-configuration.\n\nWe need to add a `title` object to our existing `scales` object. Each axis, \"x\" and \"y\", is an object within the `scales` object and can have a title along with its associated parameters (color, font, etc.).\n\nWe will add a `title` element to the `x` object within our `scales` object. Several parameters are customizable, and we will need to modify the `text` parameter to set the title for each axis and the `display` parameter to show them, as this parameter is set to `false` by default (**note the different boolean notation between JavaScript and R**: `true/false` VS `TRUE/FALSE`).\n\nThe documentation sometimes lacks examples, so we can also rely on StackOverflow: https://stackoverflow.com/questions/27910719/in-chart-js-set-chart-title-name-of-x-axis-and-y-axis. However, be careful with the version of Chart.js used, as parameters may vary.\n\nIn our JS handler, we will add an `xAxisTitle` parameter and a `yAxisTitle` parameter.\n\n``` javascript\nconst config = {\n type: 'scatter',\n data: data,\n options: {\n scales: {\n x: {\n type: 'linear',\n position: 'bottom',\n title: {\n display: true,\n text: arg.xAxisTitle\n }\n },\n y: {\n title: {\n display: true,\n text: arg.yAxisTitle\n }\n }\n }\n }\n };\n```\n\nBe cautious once again about the syntax difference between JavaScript and R. Parameters are passed in the form `display: true` rather than `display = TRUE`, for example. Confusing `:` with `=` can easily occur and result in non-functional code.\n\nIn our `R/app_server.R` file, we will add the `xAxisTitle` and `yAxisTitle` elements to the list passed as an argument to our handler.\n\n``` r\nobserveEvent(input$showscatterplot, {\n\tgolem::invoke_js(\n\t\t\"scatterplotJS\",\n\t\tlist(\n\t\t\tlabel = \"My scatterplot\",\n\t\t\tdata = purrr::transpose(\n\t\t\t\tlist(\n\t\t\t\t\tx = iris$Sepal.Length,\n\t\t\t\t\ty = iris$Sepal.Width\n\t\t\t\t)\n\t\t\t),\n\t\t\txAxisTitle = \"Sepal Length\",\n\t\t\tyAxisTitle = \"Sepal Width\"\n\t\t)\n\t)\n})\n```\n\nAnd here's the result in our application:\n\n\n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step6\n\n### Going further with the scatter plot\n\nAdditional modifications can be made to enhance the chart:\n\n - Modify the title\n \n - Change the fill color of the points and their border color\n\nHere are the resources used to produce the code that we will present shortly:\n\n - Title: https://www.chartjs.org/docs/latest/configuration/title.html\n\n The title should be included in a `plugins` object, which in turn is included in the `options` object.\n\n - Point Colors: https://www.chartjs.org/docs/latest/charts/line.html#point-styles\n\n The color of the points will be managed within the `datasets` object.\n\nWe will offer users the ability to set the chart title, its color, and the color of the points through `shiny` inputs (which will be a good way to revisit the \"update\"-related issues ๐Ÿ˜‰).\n\nBelow is a preview of the chart created here (without functional \"update\" for now):\n\n\n\nThe handler code has been completed to account for these new elements:\n\n``` javascript\t\n$(document).ready(function () {\n Shiny.addCustomMessageHandler(\"scatterplotJS\", function (arg) {\n const ctx = document.getElementById(\"myChart2\");\n\n console.log(arg);\n\n const data = {\n datasets: [\n {\n label: arg.label,\n data: arg.data,\n borderColor: arg.pointBorderColor,\n backgroundColor: arg.pointBackGroundColor,\n },\n ],\n };\n\n const plugins = {\n title: {\n display: true,\n text: arg.mainTitle,\n color: arg.mainTitleColor,\n },\n };\n\n const config = {\n type: \"scatter\",\n data: data,\n options: {\n plugins: plugins,\n scales: {\n x: {\n type: \"linear\",\n position: \"bottom\",\n title: {\n display: true,\n text: arg.xAxisTitle,\n },\n },\n y: {\n title: {\n display: true,\n text: arg.yAxisTitle,\n },\n },\n },\n },\n };\n new Chart(ctx, config);\n });\n});\n```\n\nIn `R/app_ui.R`, elements have been added to allow the user to pass the necessary parameters:\n\n``` r\nh1(\"Scatterplot\"),\ntextInput(\n\tinputId = \"scatterplot_title\",\n\tlabel = \"Scatterplot Title\",\n\tvalue = \"ChartJS rocks !\"\n),\nselectInput(\n\tinputId = \"title_color\",\n\tlabel = \"Title Color\",\n\tchoices = c(\"brown\", \"orange\", \"purple\"),\n\tselected = \"brown\"\n),\nselectInput(\n inputId = \"points_background_color\",\n\tlabel = \"Points Background Color\",\n\tchoices = c(\"red\", \"blue\", \"green\"),\n\tselected = \"red\"\n),\nactionButton(\n inputId = \"showscatterplot\",\n\tlabel = \"Show Scatterplot\"\n),\ntags$div(\n\ttags$canvas(id = \"myChart2\")\n)\n```\n\nFinally, in `R/app_server.R`, we add the necessary elements to pass the parameters to our handler:\n\n``` r\nobserveEvent(input$showscatterplot, {\n\t\tgolem::invoke_js(\n\t\t\t\"scatterplotJS\",\n\t\t\tlist(\n\t\t\t\tlabel = \"My scatterplot\",\n\t\t\t\tdata = purrr::transpose(\n\t\t\t\t\tlist(\n\t\t\t\t\t\tx = iris$Sepal.Length,\n\t\t\t\t\t\ty = iris$Sepal.Width\n\t\t\t\t\t)\n\t\t\t\t),\n\t\t\t\txAxisTitle = \"Sepal Length\",\n\t\t\t\tyAxisTitle = \"Sepal Width\",\n\t\t\t\tmainTitle = input$scatterplot_title,\n\t\t\t\tmainTitleColor = input$title_color,\n\t\t\t\tpointBorderColor = \"black\",\n\t\t\t\tpointBackGroundColor = input$points_background_color\n\t\t\t)\n\t\t)\n\t})\n```\n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step7\n\nWe still need to include the `.update()` method to account for updates to `shiny` inputs related to the title and the color of the points.\n\nWe will use the approach from the previous chart to modify our JS handler accordingly.\n\n``` javascript\t\n$(document).ready(function () {\n Shiny.addCustomMessageHandler(\"scatterplotJS\", function (arg) {\n const ctx = document.getElementById(\"myChart2\");\n\n console.log(arg);\n\n const chart2 = Chart.getChart(\"myChart2\");\n\n if (chart2 == undefined) {\n console.log(\"Creating a new chart\");\n\n const data = {\n datasets: [\n {\n label: arg.label,\n data: arg.data,\n borderColor: arg.pointBorderColor,\n backgroundColor: arg.pointBackGroundColor,\n },\n ],\n };\n\n const plugins = {\n title: {\n display: true,\n text: arg.mainTitle,\n color: arg.mainTitleColor,\n },\n };\n\n const config = {\n type: \"scatter\",\n data: data,\n options: {\n plugins: plugins,\n scales: {\n x: {\n type: \"linear\",\n position: \"bottom\",\n title: {\n display: true,\n text: arg.xAxisTitle,\n },\n },\n y: {\n title: {\n display: true,\n text: arg.yAxisTitle,\n },\n },\n },\n },\n };\n new Chart(ctx, config);\n } else {\n console.log(\"Updating an existing chart\");\n chart2.data.datasets[0].backgroundColor = arg.pointBackGroundColor;\n chart2.options.plugins.title.text = arg.mainTitle;\n chart2.options.plugins.title.color = arg.mainTitleColor;\n chart2.update();\n }\n });\n});\n```\n\nLet's observe the result:\n\n\n\nWell done! ๐ŸŽ‰\n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step8\n\n### Modifying the tooltip (advanced level)\n\nWe will look to modify the tooltip that appears when hovering over a point on the chart. In addition to changing its title, we want to display the row number from the dataset corresponding to the hovered point, as well as the corresponding values of `Sepal.Length` and `Sepal.Width`.\n\nHere are the resources used:\n\n- Tooltip title: https://www.chartjs.org/docs/latest/configuration/tooltip.html#tooltip-callbacks\n \n- Tooltip content: https://www.chartjs.org/docs/latest/configuration/tooltip.html#tooltip-callbacks and https://www.youtube.com/watch?v=anseX1ePfUw\n\nThis part will be more complex than the previous ones. But we will manage it! ๐Ÿ’ช\n\nThe `plugins` object, used previously to manage the chart title, contains a `tooltip` element, which in turn contains a `callbacks` element. It is within this element that we can modify the title and content of the tooltip. Most tooltip elements can be configured via a function call that takes a `context` element as input. This is a JavaScript object that contains several items related to the hovered point. We will explore the content of this object to extract the information we need later when customizing the tooltip content.\n\nWe modify our JS handler by including a fixed title (we could also have passed it as a parameter):\n\n``` javascript\t\nconst tooltip = {\n callbacks: {\n title: function (context) {\n return \"Tooltip title\";\n },\n },\n};\n\nconst plugins = {\n title: {\n display: true,\n text: arg.mainTitle,\n color: arg.mainTitleColor,\n },\n tooltip: tooltip\n};\n```\n\nLet's see if it works:\n\n\n\nThe application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step9\n\nLet's proceed with customizing the tooltip content!\n\nIn this step, we will modify the `label` parameter in the `tooltip` object. To refine our code, we will use the `debugger` function, which we haven't used so far! If you are familiar with using `browser()` in R, `debugger` is its JavaScript equivalent. It allows you to pause the code execution and open the browser console to explore the arguments passed to a function.\n\nLet's modify our handler:\n\n``` javascript\t\nconst tooltip = {\n callbacks: {\n title: function (context) {\n return \"Tooltip title\";\n },\n label: function(context) {\n debugger;\n }\n },\n};\n\nconst plugins = {\n title: {\n display: true,\n text: arg.mainTitle,\n color: arg.mainTitleColor,\n },\n tooltip: tooltip,\n};\n```\n\nWe add a call to the JavaScript `debugger` in the `label` function of the `callbacks` object. We restart our application:\n\n\n\nWhen hovering over a point on the chart, code execution is paused and the browser console opens. We can then explore the content of the `context` object passed to the `label` function.\n\nWe can identify the information that will be useful:\n\n- The row number in the dataset: `context.dataIndex`\n\n- The values of the point: `context.formattedValue`\n\nWe can then construct a customized tooltip (remembering to remove the `debugger` call ๐Ÿ˜‰):\n\n``` javascript\t\nconst tooltip = {\n callbacks: {\n title: function (context) {\n return \"Tooltip title\";\n },\n label: function (context) {\n lab =\n \"Line number: \" +\n context.dataIndex +\n \" values: \" +\n context.formattedValue;\n return lab;\n },\n },\n};\n\nconst plugins = {\n title: {\n display: true,\n text: arg.mainTitle,\n color: arg.mainTitleColor,\n },\n tooltip: tooltip,\n};\n```\n\n\n\nMission accomplished! ๐Ÿš€\n\nThe code for this step is available here: [https://github.com/ymansiaux/golemchartjs/tree/step10](https://github.com/ymansiaux/golemchartjs/tree/step10)\n\n## Conclusion\n\nAfter our initial foray into calling JavaScript code from R with the sweetalert2 library, we have now explored using a data visualization library.\n\nKey takeaways:\n\n- Always try to get the documentation examples working before adapting them to your application.\n- Use `jsonlite::toJSON()` to verify that the data passed is in the format expected by the library.\n- Keep in mind that sometimes you need to \"update\" or \"destroy\" objects on a web page.\n- Use `console.log()` or `debugger` to see the contents of a JavaScript object passed as an argument to a function.\n\nAfter overcoming some challenging moments, we can see the possibilities offered by JavaScript data visualization libraries. You can achieve a high degree of customization for your charts, and Chart.js offers many features. Documentation, combined with research on discussion forums, can help solve problems that may seem insurmountable at first.\n\nFeel free to dive into integrating JavaScript libraries into your `{shiny}` applications. It can be an excellent way to break new ground and offer interactive and customized charts to your users.\n\nSee you soon for new adventures! ๐Ÿš€", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/posts/post-with-code/index/execute-results/html.json b/_freeze/posts/post-with-code/index/execute-results/html.json new file mode 100644 index 0000000..80f5ea5 --- /dev/null +++ b/_freeze/posts/post-with-code/index/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "cd08df506bbb267085d3940755c8b699", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Pimping your {shiny} app with a JavaScript library : an example using sweetalert2\"\nauthor: \"Yohann Mansiaux\"\ndate: \"2024-04-30\"\ncategories: [shiny, javascript]\nimage: \"image.jpg\"\n---\n\n\n\n**You think that some of the components of `{shiny}` are not very functional or downright austere? Are you looking to implement some feature in your app but it is not available in the `{shiny}` toolbox? Take a look at JavaScript!** \n\nJavaScript is a very popular programming language that is often used to add features to web pages. With HTML and CSS, JavaScript is an essential language for web developers. The size of its user community means that if you are looking to implement a particular feature, there is a good chance that someone has already had the same need as you and has shared their code!\n\nAn other positive point (and essential for us in this case) : it is possible to integrate JavaScript libraries into a `{shiny}` application to add features that are not available by default. In addition to that, `{golem}` will help us to set everything up.\n\nNo more excuses to back down, let's go ! ๐Ÿš€\n\n\n\n## TL;DR\n\n- Going further in `{shiny}` by integrating a JavaScript library is possible!\n - We take the example of sweetalert2, which allows to display alerts that are more visually appealing than the basic ones\n - `{golem}` has several functions to make it easier for us to integrate JavaScript libraries into a `{shiny}` app\n - This example is rather simple. The integration of libraries is sometimes harder because the documentation might be scarse or the library might be more complex to use \n\n## Import sweetalert2 into a `{shiny}` app created with `{golem}`\n\n### sweetalert2\n\n- sweetalert2 is a JavaScript library that allows you to display alerts that are more visually appealing than the basic ones\n- It is very well documented\n- It is very popular (more than 16000 \"stars\" on GitHub at the time of publication of this article)\n\nLet's take a look at the possibilities offered by sweetalert2: https://sweetalert2.github.io/\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](./img/01-sweetalertaccueil.png){width=70%}\n:::\n:::\n\n\n\nIf you click on \"Show normal alert\", you will see a classic alert while clicking on \"Show success message\", you will see a sweetalert2 alert.\n\nThe first one has a rather austere design while the second one is more modern and more pleasant to the eye, it will probably offer a better user experience.\n\nFeel free to play with the different types of alerts offered by sweetalert2 to get an idea of what is possible with this library by visiting the examples section.\n\n### Add the necessary dependencies to the `{shiny}` app\n\n**The following sections assume that you have already created a `{shiny}` app with `{golem}`.**\n\nIf this is not the case and you want to know more about `{golem}`, I invite you to consult the official documentation.\n\nTo add sweetalert2 to your `{shiny}` app, you will need to find a way to incorporate the files needed for its operation into your application.\n\nTwo solutions are available to you:\n\n- Use a \"CDN\" (Content Delivery Network) to load the files from a third-party server. The CDN will be the equivalent of a CRAN for JavaScript libraries. Concretely, we will ask our application to point to the sources of sweetalert2, hosted on a remote server.\n\n- Download the files needed for its operation and integrate them directly into your application. If your application is intended to be used on a machine that is not connected to the Internet, you will inevitably have to go through this step.\n\n**Don't panic! We will see both methods**\n\n#### Where to find the elements I need?\n\nThe sweetalert2 documentation is very well done. You will find all the information you need to integrate the library into your application from the Download section.\n\nHowever, you will need to learn how to identify the elements you need to integrate sweetalert2 into your application.\n\n**Looking for the CDN**\n\nIn the \"Download & Install\" section, you will find a link to the sweetalert2 CDN. This is the link that we will have to add to our application in order to use the library.\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](img/02-cdn.png){width=80%}\n:::\n:::\n\n\n\nWhen you click on the link, you will arrive on a page that looks like this:\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](img/03-cdn_site.png){width=100%}\n:::\n:::\n\n\n\nWhat we are interested in here is the link in the ` + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+
+
+

Boost your shiny app with sparkling data visualizations: A deep dive into Chart.js JavaScript library

+
+
shiny
+
javascript
+
+
+
+ + +
+ +
+
Author
+
+

Yohann Mansiaux

+
+
+ +
+
Published
+
+

April 30, 2024

+
+
+ + +
+ + +
+ + + + +
+ + + + + +

Letโ€™s continue our exploration of integrating JavaScript code into a {shiny} application! We will show how to move beyond the classic graphs produced in base R or with {ggplot2} to explore the interactive dataviz production libraries of JavaScript, particularly the Chart.js library.

+

If you missed my first article on integrating JavaScript libraries into a {shiny} application I invite you to read it before diving into this one.

+

Crucial concepts are covered and will not be repeated here. We particularly think about:

+
    +
  • How to add a JavaScript libraryโ€™s dependencies to a {shiny} application
    +
  • +
  • How to call JavaScript code from R
  • +
+
+

TL;DR

+
    +
  • Creating interactive charts that go beyond the usual dataviz produced in R is possible by integrating a JavaScript library!
  • +
    • +
    • We use the example of Chart.js, a very popular JavaScript dataviz library
    • +
    • Specificities related to integrating Chart.js into a {shiny} application are addressed, including passing data from R to JavaScript and the differences in expected data formats.
    • +
    • Weโ€™ll see how to make sure our JavaScript code is working properly by using the web browserโ€™s console.
    • +
  • +
+
+
+

Importing Chart.js into a {shiny} app created with {golem}

+
    +
  • Chart.js is a JavaScript library that allows you to create many types of charts (bars, lines, radar, etc.) and customize them as you wish
  • +
  • It is very well documented
  • +
  • It is the most popular JavaScript dataviz library on GitHub (over 60,000 โ€œstarsโ€ at the time of this articleโ€™s publication)
  • +
+

To get an overview of the possibilities offered by Chart.js, visit the official page: https://www.chartjs.org/docs/latest/samples/information.html

+
+

Add the dependencies to Chart.js in your {shiny} app

+

The following sections assume that you have already created a {shiny} app with {golem}.

+

If this is not the case and you want to learn more about {golem}, I invite you to consult the official documentation.

+

To add Chart.js to your {shiny} app, you will need to find a way to incorporate the necessary files for its operation into your application. As we saw in our previous article, two solutions are possible.

+
    +
  • Use a โ€œCDNโ€ (Content Delivery Network) to load the files from a third-party server.
  • +
  • Download the necessary files and integrate them directly into the application.
  • +
+

We will use the โ€œCDNโ€ method here.

+

Go to the โ€œGetting Startedโ€ section of Chart.js documentation .

+
+
+
+
+

+
+
+
+
+

We retrieve the CDN URL and store this information for later use.

+

After creating the skeleton of an application via {golem}, we will add the Chart.js dependency.

+

Letโ€™s open the R/app_ui.R file of our application and add the link we copied earlier into the body of the golem_add_external_resources() function.

+
golem_add_external_resources <- function() {
+  add_resource_path(
+    "www",
+    app_sys("app/www")
+  )
+  
+  tags$head(
+    favicon(),
+    bundle_resources(
+      path = app_sys("app/www"),
+      app_title = "chartJS"
+    ),
+    # Add here other external resources
+    # for example, you can add shinyalert::useShinyalert()
+    # Chart.js
+    tags$script(src = "https://cdn.jsdelivr.net/npm/chart.js")
+  )
+}
+
+
+

How to know if Chart.js is properly imported ?

+

The โ€œGetting Startedโ€ section previously consulted to retrieve the CDN link indicates that it is necessary to incorporate the HTML <canvas> tag into our application to display a Chart.js chart. We add this element to the R/app_ui.R file of our application.

+
app_ui <- function(request) {
+  tagList(
+    # Leave this function for adding external resources
+    golem_add_external_resources(),
+    # Your application UI logic
+    fluidPage(
+      h1("golemchartjs"),
+      tags$div(
+        tags$canvas(id="myChart")
+      )
+    )
+  )
+}
+

To verify that Chart.js is properly imported into our application, we run our app with golem::run_dev(), and the rest will take place in the web browser.

+

NB: The following screenshots were taken using the Google Chrome browser.

+

In the window of our application, right-click and then select โ€œInspectโ€. In the new window that opens, choose the โ€œConsoleโ€ tab and type the command to generate a Chart.js chart, as indicated once again in the โ€œGetting Startedโ€ section between the HTML script tags.

+

The code to copy and paste into the console is the following:

+
  const ctx = document.getElementById('myChart');
+
+  new Chart(ctx, {
+    type: 'bar',
+    data: {
+      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
+      datasets: [{
+        label: '# of Votes',
+        data: [12, 19, 3, 5, 2, 3],
+        borderWidth: 1
+      }]
+    },
+    options: {
+      scales: {
+        y: {
+          beginAtZero: true
+        }
+      }
+    }
+  });
+

+

The chart from the demo page appears as expected! ๐ŸŽ‰

+

We can move on! ๐Ÿ˜Š

+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step1

+
+
+
+

Creating a Bar Chart with Chart.js

+

The code used previously allowed us to verify that Chart.js was properly imported into our application. Now, we will see how to create a Chart.js chart from our {shiny} application. The goal is to produce bar charts for various datasets with customizable options based on user choices.

+

Letโ€™s revisit the code executed previously:

+
  const ctx = document.getElementById('myChart');
+
+  new Chart(ctx, {
+    type: 'bar',
+    data: {
+      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
+      datasets: [{
+        label: '# of Votes',
+        data: [12, 19, 3, 5, 2, 3],
+        borderWidth: 1
+      }]
+    },
+    options: {
+      scales: {
+        y: {
+          beginAtZero: true
+        }
+      }
+    }
+  });
+

We could imagine passing the labels, label, data, and borderWidth elements as function parameters.

+

Letโ€™s go! ๐Ÿš€

+
+

Creating a JavaScript code usable from R

+

We saw in our previous article that the way to call JavaScript code from R is to use a โ€œJS handlerโ€. To do this, go to the dev/02_dev.R file! We add the following line in the โ€œExternal Resourcesโ€ section:

+
golem::add_js_handler("barchartJS")
+

We fill in the skeleton by indicating โ€œbarchartJSโ€ as the name of our handler and adding the JavaScript code we saw previously.

+
$(document).ready(function () {
+  Shiny.addCustomMessageHandler("barchartJS", function (arg) {
+    const ctx = document.getElementById("myChart");
+
+    new Chart(ctx, {
+      type: "bar",
+      data: {
+        labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+        datasets: [
+          {
+            label: "# of Votes",
+            data: [12, 19, 3, 5, 2, 3],
+            borderWidth: 1,
+          },
+        ],
+      },
+      options: {
+        scales: {
+          y: {
+            beginAtZero: true,
+          },
+        },
+      },
+    });
+  });
+});
+

We replace the labels, label, data, and borderWidth parameters, which are hardcoded here, with the future elements passed as arguments to our handler. The notation to use here will be arg.param_name to access the values passed by our {shiny} application. The . notation is a JavaScript convention for accessing properties of an object. To draw a parallel with R, itโ€™s somewhat like using arg$param_name.

+

At the beginning of our handler, we add a call to the console.log() function to check the contents of the arg element from the JS console. This will allow us to verify that the elements passed from R are correctly transmitted to our handler.

+
$( document ).ready(function() {
+  Shiny.addCustomMessageHandler('barchartJS', function(arg) {
+    console.log(arg);    
+    const ctx = document.getElementById('myChart');
+
+    new Chart(ctx, {
+      type: 'bar',
+      data: {
+        labels: arg.labels,
+        datasets: [{
+          label: arg.label,
+          data: arg.data,
+          borderWidth: arg.borderWidth
+        }]
+      },
+      options: {
+        scales: {
+          y: {
+            beginAtZero: true
+          }
+        }
+      }
+    }); 
+  })
+});
+

We will add elements to the R/app_ui.R file to generate the parameters to be passed to our handler:

+
    +
  • arg.labels will be a vector of 5 character strings, randomly chosen from the letters of the latin alphabet.
  • +
  • arg.label will be a character string, randomly chosen from the letters of the latin alphabet.
  • +
  • arg.data will be a vector of 5 integer numbers, randomly chosen between 1 and 100.
  • +
  • arg.borderWidth will be an integer, randomly chosen between 1 and 5.
  • +
+

The display of the chart will be triggered by clicking a โ€œShow Barplotโ€ button.

+

Here is the content of our R/app_ui.R file:

+
app_ui <- function(request) {
+    tagList(
+        # Leave this function for adding external resources
+        golem_add_external_resources(),
+        # Your application UI logic
+        fluidPage(
+            h1("golemchartjs"),
+            actionButton(
+                inputId = "showbarplot",
+                label = "Show Barplot"
+            ),
+            tags$div(
+                tags$canvas(id = "myChart")
+            )
+        )
+    )
+}
+

And the content of the R/app_server.R file :

+
app_server <- function(input, output, session) {
+    observeEvent(input$showbarplot, {
+        app_labels <- sample(letters, 5)
+        app_label <- paste0(sample(letters, 10), collapse = "")
+        app_data <- sample(1:100, 5)
+        app_borderWidth <- sample(1:5, 1)
+
+        golem::invoke_js(
+            "barchartJS",
+            list(
+                labels = app_labels,
+                label = app_label,
+                data = app_data,
+                borderWidth = app_borderWidth
+            )
+        )
+    })
+}
+

Here are the key points to remember:

+
    +
  • The first parameter in the call to golem::invoke_js() is the name of the JavaScript handler.
  • +
  • The following parameters are the elements to be passed as arguments to our handler. They should be passed in a named list where the names correspond to the elements in the arg object of our handler.
  • +
+

Letโ€™s run our application with golem::run_dev() and verify that everything works as expected!

+

+

Congratulations! ๐Ÿ‘

+

In addition to the displayed chart, we can see that the JavaScript console in the browser correctly shows the content of the arg object, including its 4 sub-elements: labels, label, data, and borderWidth.

+

And if you click the button again, what happens?

+

+

The chart does not update; it remains stuck on the first chart! ๐Ÿ˜ฎ

+

The JavaScript console indicates that the arg object has indeed been updated, but the chart does not refresh. Additionally, an error message appears in the JavaScript console: โ€œError: Canvas is already in use. Chart with ID โ€˜0โ€™ must be destroyed before the canvas with ID โ€˜myChartโ€™ can be reused.โ€

+

Letโ€™s try to understand whatโ€™s happening: in the R/app_ui.R file, we added a canvas element with the ID โ€œmyChartโ€ (with tags$canvas(id = "myChart")). This element is used to display the chart. When we click the โ€œShow Barplotโ€ button, a new chart is generated and displayed in this element. However, the previous chart is not destroyed, and the error message indicates that the โ€œcanvasโ€ is already in use.

+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step2

+
+
+

Why isnโ€™t the chart updating?

+

To find the answer, we need to refer back to the Chart.js documentation. We can read in the โ€œ.destroy()โ€ section that in order to reuse the HTML โ€œcanvasโ€ element for displaying a new chart, it is necessary to destroy the previous chart.

+

There is also a command โ€œ.update()โ€ for updating an existing chart. This method seems more appropriate here, as we are using the same type of chart, with only a few parameters changing. The .update() method allows updating an existing chart without having to destroy and recreate it, which will be less โ€œbrutalโ€ visually (with a chart disappearing and then reappearing). However, the .destroy() method should be kept in mind for cases where we want to radically change the type of chart, for example.

+

Updating a chart implies that a chart has already been generated once. Therefore, we need to modify our JavaScript handler to account for this and find a way to detect the existence of a chart on our page. For this, we will refer again to the Chart.js documentation, particularly the getChart method: https://www.chartjs.org/docs/latest/developers/api.html#static-getchart-key.

+

The command to use is in the following form: const chart = Chart.getChart("canvas-id");. According to the documentation, if the chart exists, the variable chart will contain the Chart.js object associated with the HTML โ€œcanvasโ€ element. If the chart does not exist, the variable chart will be undefined.

+

For this command to work, we need to replace โ€œcanvas-idโ€ with the ID of our โ€œcanvasโ€, which is โ€œmyChartโ€ here: const chart = Chart.getChart("myChart");

+

Letโ€™s restart our application. We indeed find that the chart object is undefined as long as the chart has not been created, and it correctly reflects this status afterwards.

+

+

We can adapt our code as follows:

+
    +
  • If chart is undefined, we create a new chart.

  • +
  • If chart is not undefined, we update the existing chart.

  • +
+

We adapt our handler by referring to the documentation for the .update() method: https://www.chartjs.org/docs/latest/developers/api.html#update-mode

+
$(document).ready(function () {
+  Shiny.addCustomMessageHandler("barchartJS", function (arg) {
+    console.log(arg);
+    const ctx = document.getElementById("myChart");
+
+    const chart = Chart.getChart("myChart");
+
+    if (chart == undefined) {
+      console.log("Creating a new chart");
+      new Chart(ctx, {
+        type: "bar",
+        data: {
+          labels: arg.labels,
+          datasets: [
+            {
+              label: arg.label,
+              data: arg.data,
+              borderWidth: arg.borderWidth,
+            },
+          ],
+        },
+        options: {
+          scales: {
+            y: {
+              beginAtZero: true,
+            },
+          },
+        },
+      });
+    } else {
+      console.log("Updating an existing chart");
+      chart.data.labels = arg.labels;
+      chart.data.datasets[0].label = arg.label;
+      chart.data.datasets[0].data = arg.data;
+      chart.data.datasets[0].borderWidth = arg.borderWidth;
+      chart.update();
+    }
+  });
+});
+

This example is a bit more complex than those seen so far:

+
    +
  • Retrieve the Chart.js object associated with the HTML โ€œcanvasโ€ element using the method Chart.getChart("myChart").

  • +
  • Check if this object is undefined: if it is, use the code that has been working until now to create a new chart.

  • +
  • If it is not undefined, overwrite the configuration elements you want to update and then use the .update() method. Note the specifics of handling configuration elements: chart.data.labels = arg.labels for the labels, chart.data.datasets[0].label = arg.label for the label, etc. Use . to access object properties, with each . allowing access to a deeper level of โ€œdepthโ€. It is also important to note that array indexing starts at 0 in JavaScript, not at 1 like in R.

  • +
+

After all these efforts, letโ€™s see if everything is back in order ๐Ÿ˜„!

+

+

Phew, everything is OK this time! ๐Ÿฅฒ

+

Weโ€™ve touched on a more complex case of using a JavaScript library in a {shiny} application. It is crucial to understand the libraryโ€™s functioning by delving into the depths of its documentation. Moreover, one of the advantages of using a very popular library is that you can often find help on StackOverflow ๐Ÿ˜Š (here is an example of using the .destroy() method).

+

Feel free to go further in customizing your chart, such as changing the bar colors: https://www.chartjs.org/docs/latest/general/colors.html and https://www.chartjs.org/docs/latest/charts/bar.html.

+

The best way to learn is to try reproducing examples from the documentation.

+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step3

+
+
+
+

Creating a Scatter Plot with Chart.js

+

We will attempt to create a scatter plot with Chart.js. To develop our code, we will rely on the Chart.js documentation: https://www.chartjs.org/docs/latest/charts/scatter.html.

+

As before, our code will be stored in a JS handler. Therefore, we will add a new handler in the dev/02_dev.R file:

+
golem::add_js_handler("scatterplotJS")
+

The documentation is slightly different from that provided for bar charts. We will need to adapt our handler accordingly. We identify an element config, which will include the type, data, and options elements we have already seen. There is also a data element containing datasets and labels.

+

We will fill in the skeleton of our handler with the JavaScript code from the Chart.js documentation. Initially, we will leave out the โ€œupdateโ€ part.

+
$(document).ready(function () {
+  Shiny.addCustomMessageHandler("scatterplotJS", function (arg) {
+    const ctx = document.getElementById("myChart2");
+
+    const data = {
+      datasets: [
+        {
+          label: "Scatter Dataset",
+          data: [
+            {
+              x: -10,
+              y: 0,
+            },
+            {
+              x: 0,
+              y: 10,
+            },
+            {
+              x: 10,
+              y: 5,
+            },
+            {
+              x: 0.5,
+              y: 5.5,
+            },
+          ],
+          backgroundColor: "rgb(255, 99, 132)",
+        },
+      ],
+    };
+
+    const config = {
+      type: "scatter",
+      data: data,
+      options: {
+        scales: {
+          x: {
+            type: "linear",
+            position: "bottom",
+          },
+        },
+      },
+    };
+    new Chart(ctx, config);
+  });
+});
+

Our JS handler โ€œscatterplotJSโ€ is ready! We need to add the โ€œdivโ€ and โ€œcanvasโ€ to the UI to display the generated chart. We need to modify the HTML ID of our โ€œcanvasโ€ to avoid any conflict with the bar chart. It will be named โ€œmyChart2โ€ here.

+

Note that there is a slightly different syntax compared to the code used for the bar chart, where the call to โ€œnew Chartโ€ was made directly with the data and options elements. Here, we store these elements in data and config variables before passing them to new Chart.

+

Next, we add the following to the R/app_ui.R file:

+
h1("Scatterplot"),
+actionButton(
+    inputId = "showscatterplot",
+    label = "Show Scatterplot"
+),
+tags$div(
+    tags$canvas(id = "myChart2")
+)
+

We add the following to the R/app_server.R file:

+
  observeEvent(input$showscatterplot, {
+    golem::invoke_js(
+      "scatterplotJS",
+      list(
+      )
+    )
+  })
+

Our handler does not use any elements passed from R. However, it is necessary to pass an empty list as an argument to ensure the proper functioning of golem::invoke_js().

+

Letโ€™s run your application with golem::run_dev() and verify that everything works as expected!

+

+

The chart from the documentation works! ๐ŸŽ‰

+

Now, letโ€™s go further by passing our own data as input.

+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step4

+
+

An example with the iris dataset

+

We will use the iris dataset to generate a scatter plot. We will pass as arguments to our JS handler the data from the Sepal.Length and Sepal.Width columns.

+

As with the bar chart, we will use elements passed from R through the arg object in JavaScript.

+

We modify the data object to include a legend title and, most importantly, the data. To observe the elements passed from R, we add a call to console.log().

+
console.log(arg);
+const data = {
+  datasets: [
+    {
+      label: arg.label,
+      data: arg.data,
+      backgroundColor: "rgb(255, 99, 132)",
+    },
+  ],
+};
+

As a reminder, in the example from the documentation, the data is passed in the form of an โ€œarray of dictionariesโ€. Each dictionary contains the keys x and y for the point coordinates.

+
data: [{
+    x: -10,
+    y: 0
+  }, {
+    x: 0,
+    y: 10
+  }, {
+    x: 10,
+    y: 5
+  }, {
+    x: 0.5,
+    y: 5.5
+}]
+

Letโ€™s try to pass the contents of the Sepal.Length and Sepal.Width columns via a list. We make the following modification in R/app_server.R:

+
observeEvent(input$showscatterplot, {
+    golem::invoke_js(
+        "scatterplotJS",
+        list(
+            label = "My scatterplot",
+            data = list(
+                x = iris$Sepal.Length,
+                y = iris$Sepal.Width
+            )
+        )
+    )
+})
+

We restart our application, and unfortunately, nothing shows up!

+

+

Thanks to the console.log() call in our handler, we can observe the content of the arg object in the JavaScript console of the browser. We notice that the data passed is not in the correct format. Here, we get an array of two elements, the first containing the values of Sepal.Length and the second containing the values of Sepal.Width, which is not the expected format.

+

Here, we need to do some work on the R side to transform our data into the expected format.

+

If we display a JSON preview of the data we passed as input, indeed the rendering is incorrect.

+
+
jsonlite::toJSON(
+    list(
+        x = iris$Sepal.Length,
+        y = iris$Sepal.Width
+    )
+)
+
+
+
+
{"x":[5.1,4.9,4.7,4.6,5,5.4,4.6,5,4.4,4.9],"y":[3.5,3,3.2,3.1,3.6,3.9,3.4,3.4,2.9,3.1]} 
+
+
+

For manipulating lists, the {purrr} package is a top choice.

+
+
new_data <- purrr::transpose(
+    list(
+        x = iris$Sepal.Length,
+        y = iris$Sepal.Width
+    )
+)
+jsonlite::toJSON(
+    new_data,
+    auto_unbox = TRUE
+)
+
+
+
+
[{"x":5.1,"y":3.5},{"x":4.9,"y":3},{"x":4.7,"y":3.2},{"x":4.6,"y":3.1},{"x":5,"y":3.6},{"x":5.4,"y":3.9},{"x":4.6,"y":3.4},{"x":5,"y":3.4},{"x":4.4,"y":2.9},{"x":4.9,"y":3.1}] 
+
+
+

The rendering seems to be more in line with what is expected by Chart.js. Therefore, we will modify our code to pass the data in this manner.

+
observeEvent(input$showscatterplot, {
+    golem::invoke_js(
+        "scatterplotJS",
+        list(
+            label = "My scatterplot",
+            data = purrr::transpose(
+                list(
+                    x = iris$Sepal.Length,
+                    y = iris$Sepal.Width
+                )
+            )
+        )
+    )
+})
+

Letโ€™s observe the result:

+

+

This time itโ€™s good! ๐Ÿ˜Š We can see in the JavaScript console that the data has indeed been passed in the correct format.

+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step5

+
+
+

A Little Extra Polish

+

Our chart still lacks titles for the axes! To find out how to do this, the documentation comes to our rescue once again: https://www.chartjs.org/docs/latest/axes/labelling.html#scale-title-configuration.

+

We need to add a title object to our existing scales object. Each axis, โ€œxโ€ and โ€œyโ€, is an object within the scales object and can have a title along with its associated parameters (color, font, etc.).

+

We will add a title element to the x object within our scales object. Several parameters are customizable, and we will need to modify the text parameter to set the title for each axis and the display parameter to show them, as this parameter is set to false by default (note the different boolean notation between JavaScript and R: true/false VS TRUE/FALSE).

+

The documentation sometimes lacks examples, so we can also rely on StackOverflow: https://stackoverflow.com/questions/27910719/in-chart-js-set-chart-title-name-of-x-axis-and-y-axis. However, be careful with the version of Chart.js used, as parameters may vary.

+

In our JS handler, we will add an xAxisTitle parameter and a yAxisTitle parameter.

+
const config = {
+    type: 'scatter',
+    data: data,
+    options: {
+      scales: {
+        x: {
+          type: 'linear',
+          position: 'bottom',
+          title: {
+            display: true,
+            text: arg.xAxisTitle
+            }
+          },
+        y: {
+          title: {
+            display: true,
+            text: arg.yAxisTitle
+          }
+        }
+      }
+    }
+  };
+

Be cautious once again about the syntax difference between JavaScript and R. Parameters are passed in the form display: true rather than display = TRUE, for example. Confusing : with = can easily occur and result in non-functional code.

+

In our R/app_server.R file, we will add the xAxisTitle and yAxisTitle elements to the list passed as an argument to our handler.

+
observeEvent(input$showscatterplot, {
+    golem::invoke_js(
+        "scatterplotJS",
+        list(
+            label = "My scatterplot",
+            data = purrr::transpose(
+                list(
+                    x = iris$Sepal.Length,
+                    y = iris$Sepal.Width
+                )
+            ),
+            xAxisTitle = "Sepal Length",
+            yAxisTitle = "Sepal Width"
+        )
+    )
+})
+

And hereโ€™s the result in our application:

+

+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step6

+
+
+

Going further with the scatter plot

+

Additional modifications can be made to enhance the chart:

+
    +
  • Modify the title

  • +
  • Change the fill color of the points and their border color

  • +
+

Here are the resources used to produce the code that we will present shortly:

+
    +
  • Title: https://www.chartjs.org/docs/latest/configuration/title.html

    +

    The title should be included in a plugins object, which in turn is included in the options object.

  • +
  • Point Colors: https://www.chartjs.org/docs/latest/charts/line.html#point-styles

    +

    The color of the points will be managed within the datasets object.

  • +
+

We will offer users the ability to set the chart title, its color, and the color of the points through shiny inputs (which will be a good way to revisit the โ€œupdateโ€-related issues ๐Ÿ˜‰).

+

Below is a preview of the chart created here (without functional โ€œupdateโ€ for now):

+

+

The handler code has been completed to account for these new elements:

+
$(document).ready(function () {
+  Shiny.addCustomMessageHandler("scatterplotJS", function (arg) {
+    const ctx = document.getElementById("myChart2");
+
+    console.log(arg);
+
+    const data = {
+      datasets: [
+        {
+          label: arg.label,
+          data: arg.data,
+          borderColor: arg.pointBorderColor,
+          backgroundColor: arg.pointBackGroundColor,
+        },
+      ],
+    };
+
+    const plugins = {
+      title: {
+        display: true,
+        text: arg.mainTitle,
+        color: arg.mainTitleColor,
+      },
+    };
+
+    const config = {
+      type: "scatter",
+      data: data,
+      options: {
+        plugins: plugins,
+        scales: {
+          x: {
+            type: "linear",
+            position: "bottom",
+            title: {
+              display: true,
+              text: arg.xAxisTitle,
+            },
+          },
+          y: {
+            title: {
+              display: true,
+              text: arg.yAxisTitle,
+            },
+          },
+        },
+      },
+    };
+    new Chart(ctx, config);
+  });
+});
+

In R/app_ui.R, elements have been added to allow the user to pass the necessary parameters:

+
h1("Scatterplot"),
+textInput(
+    inputId = "scatterplot_title",
+    label = "Scatterplot Title",
+    value = "ChartJS rocks !"
+),
+selectInput(
+    inputId = "title_color",
+    label = "Title Color",
+    choices = c("brown", "orange", "purple"),
+    selected = "brown"
+),
+selectInput(
+  inputId = "points_background_color",
+    label = "Points Background Color",
+    choices = c("red", "blue", "green"),
+    selected = "red"
+),
+actionButton(
+  inputId = "showscatterplot",
+    label = "Show Scatterplot"
+),
+tags$div(
+    tags$canvas(id = "myChart2")
+)
+

Finally, in R/app_server.R, we add the necessary elements to pass the parameters to our handler:

+
observeEvent(input$showscatterplot, {
+        golem::invoke_js(
+            "scatterplotJS",
+            list(
+                label = "My scatterplot",
+                data = purrr::transpose(
+                    list(
+                        x = iris$Sepal.Length,
+                        y = iris$Sepal.Width
+                    )
+                ),
+                xAxisTitle = "Sepal Length",
+                yAxisTitle = "Sepal Width",
+                mainTitle = input$scatterplot_title,
+                mainTitleColor = input$title_color,
+                pointBorderColor = "black",
+                pointBackGroundColor = input$points_background_color
+            )
+        )
+    })
+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step7

+

We still need to include the .update() method to account for updates to shiny inputs related to the title and the color of the points.

+

We will use the approach from the previous chart to modify our JS handler accordingly.

+
$(document).ready(function () {
+  Shiny.addCustomMessageHandler("scatterplotJS", function (arg) {
+    const ctx = document.getElementById("myChart2");
+
+    console.log(arg);
+
+    const chart2 = Chart.getChart("myChart2");
+
+    if (chart2 == undefined) {
+      console.log("Creating a new chart");
+
+      const data = {
+        datasets: [
+          {
+            label: arg.label,
+            data: arg.data,
+            borderColor: arg.pointBorderColor,
+            backgroundColor: arg.pointBackGroundColor,
+          },
+        ],
+      };
+
+      const plugins = {
+        title: {
+          display: true,
+          text: arg.mainTitle,
+          color: arg.mainTitleColor,
+        },
+      };
+
+      const config = {
+        type: "scatter",
+        data: data,
+        options: {
+          plugins: plugins,
+          scales: {
+            x: {
+              type: "linear",
+              position: "bottom",
+              title: {
+                display: true,
+                text: arg.xAxisTitle,
+              },
+            },
+            y: {
+              title: {
+                display: true,
+                text: arg.yAxisTitle,
+              },
+            },
+          },
+        },
+      };
+      new Chart(ctx, config);
+    } else {
+      console.log("Updating an existing chart");
+      chart2.data.datasets[0].backgroundColor = arg.pointBackGroundColor;
+      chart2.options.plugins.title.text = arg.mainTitle;
+      chart2.options.plugins.title.color = arg.mainTitleColor;
+      chart2.update();
+    }
+  });
+});
+

Letโ€™s observe the result:

+

+

Well done! ๐ŸŽ‰

+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step8

+
+
+

Modifying the tooltip (advanced level)

+

We will look to modify the tooltip that appears when hovering over a point on the chart. In addition to changing its title, we want to display the row number from the dataset corresponding to the hovered point, as well as the corresponding values of Sepal.Length and Sepal.Width.

+

Here are the resources used:

+
    +
  • Tooltip title: https://www.chartjs.org/docs/latest/configuration/tooltip.html#tooltip-callbacks

  • +
  • Tooltip content: https://www.chartjs.org/docs/latest/configuration/tooltip.html#tooltip-callbacks and https://www.youtube.com/watch?v=anseX1ePfUw

  • +
+

This part will be more complex than the previous ones. But we will manage it! ๐Ÿ’ช

+

The plugins object, used previously to manage the chart title, contains a tooltip element, which in turn contains a callbacks element. It is within this element that we can modify the title and content of the tooltip. Most tooltip elements can be configured via a function call that takes a context element as input. This is a JavaScript object that contains several items related to the hovered point. We will explore the content of this object to extract the information we need later when customizing the tooltip content.

+

We modify our JS handler by including a fixed title (we could also have passed it as a parameter):

+
const tooltip = {
+  callbacks: {
+    title: function (context) {
+      return "Tooltip title";
+    },
+  },
+};
+
+const plugins = {
+  title: {
+    display: true,
+    text: arg.mainTitle,
+    color: arg.mainTitleColor,
+  },
+  tooltip: tooltip
+};
+

Letโ€™s see if it works:

+

+

The application code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step9

+

Letโ€™s proceed with customizing the tooltip content!

+

In this step, we will modify the label parameter in the tooltip object. To refine our code, we will use the debugger function, which we havenโ€™t used so far! If you are familiar with using browser() in R, debugger is its JavaScript equivalent. It allows you to pause the code execution and open the browser console to explore the arguments passed to a function.

+

Letโ€™s modify our handler:

+
const tooltip = {
+  callbacks: {
+    title: function (context) {
+      return "Tooltip title";
+    },
+    label: function(context) {
+      debugger;
+    }
+  },
+};
+
+const plugins = {
+  title: {
+    display: true,
+    text: arg.mainTitle,
+    color: arg.mainTitleColor,
+  },
+  tooltip: tooltip,
+};
+

We add a call to the JavaScript debugger in the label function of the callbacks object. We restart our application:

+

+

When hovering over a point on the chart, code execution is paused and the browser console opens. We can then explore the content of the context object passed to the label function.

+

We can identify the information that will be useful:

+
    +
  • The row number in the dataset: context.dataIndex

  • +
  • The values of the point: context.formattedValue

  • +
+

We can then construct a customized tooltip (remembering to remove the debugger call ๐Ÿ˜‰):

+
const tooltip = {
+  callbacks: {
+    title: function (context) {
+      return "Tooltip title";
+    },
+    label: function (context) {
+      lab =
+        "Line number: " +
+        context.dataIndex +
+        " values: " +
+        context.formattedValue;
+      return lab;
+      },
+  },
+};
+
+const plugins = {
+  title: {
+    display: true,
+    text: arg.mainTitle,
+    color: arg.mainTitleColor,
+  },
+  tooltip: tooltip,
+};
+

+

Mission accomplished! ๐Ÿš€

+

The code for this step is available here: https://github.com/ymansiaux/golemchartjs/tree/step10

+
+
+
+

Conclusion

+

After our initial foray into calling JavaScript code from R with the sweetalert2 library, we have now explored using a data visualization library.

+

Key takeaways:

+
    +
  • Always try to get the documentation examples working before adapting them to your application.
  • +
  • Use jsonlite::toJSON() to verify that the data passed is in the format expected by the library.
  • +
  • Keep in mind that sometimes you need to โ€œupdateโ€ or โ€œdestroyโ€ objects on a web page.
  • +
  • Use console.log() or debugger to see the contents of a JavaScript object passed as an argument to a function.
  • +
+

After overcoming some challenging moments, we can see the possibilities offered by JavaScript data visualization libraries. You can achieve a high degree of customization for your charts, and Chart.js offers many features. Documentation, combined with research on discussion forums, can help solve problems that may seem insurmountable at first.

+

Feel free to dive into integrating JavaScript libraries into your {shiny} applications. It can be an excellent way to break new ground and offer interactive and customized charts to your users.

+

See you soon for new adventures! ๐Ÿš€

+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/docs/posts/post-with-code/image.jpg b/docs/posts/post-with-code/image.jpg index 3ec04c8..16093a1 100644 Binary files a/docs/posts/post-with-code/image.jpg and b/docs/posts/post-with-code/image.jpg differ diff --git a/docs/posts/post-with-code/img/01-sweetalertaccueil.png b/docs/posts/post-with-code/img/01-sweetalertaccueil.png new file mode 100644 index 0000000..a7d6ac7 Binary files /dev/null and b/docs/posts/post-with-code/img/01-sweetalertaccueil.png differ diff --git a/docs/posts/post-with-code/img/02-cdn.png b/docs/posts/post-with-code/img/02-cdn.png new file mode 100644 index 0000000..3346844 Binary files /dev/null and b/docs/posts/post-with-code/img/02-cdn.png differ diff --git a/docs/posts/post-with-code/img/03-cdn_site.png b/docs/posts/post-with-code/img/03-cdn_site.png new file mode 100644 index 0000000..9072d50 Binary files /dev/null and b/docs/posts/post-with-code/img/03-cdn_site.png differ diff --git a/docs/posts/post-with-code/img/04-import_ok.png b/docs/posts/post-with-code/img/04-import_ok.png new file mode 100644 index 0000000..51af427 Binary files /dev/null and b/docs/posts/post-with-code/img/04-import_ok.png differ diff --git a/docs/posts/post-with-code/img/05-console_js.png b/docs/posts/post-with-code/img/05-console_js.png new file mode 100644 index 0000000..7487726 Binary files /dev/null and b/docs/posts/post-with-code/img/05-console_js.png differ diff --git a/docs/posts/post-with-code/img/06_swal_fire.png b/docs/posts/post-with-code/img/06_swal_fire.png new file mode 100644 index 0000000..78c9754 Binary files /dev/null and b/docs/posts/post-with-code/img/06_swal_fire.png differ diff --git a/docs/posts/post-with-code/img/07_exemple_alert1.png b/docs/posts/post-with-code/img/07_exemple_alert1.png new file mode 100644 index 0000000..fcd4850 Binary files /dev/null and b/docs/posts/post-with-code/img/07_exemple_alert1.png differ diff --git a/docs/posts/post-with-code/img/08_exemple_alert2.png b/docs/posts/post-with-code/img/08_exemple_alert2.png new file mode 100644 index 0000000..559d069 Binary files /dev/null and b/docs/posts/post-with-code/img/08_exemple_alert2.png differ diff --git a/docs/posts/post-with-code/img/09_demo.gif b/docs/posts/post-with-code/img/09_demo.gif new file mode 100644 index 0000000..cd5d41a Binary files /dev/null and b/docs/posts/post-with-code/img/09_demo.gif differ diff --git a/docs/posts/post-with-code/img/10_demo2.gif b/docs/posts/post-with-code/img/10_demo2.gif new file mode 100644 index 0000000..0b06cbb Binary files /dev/null and b/docs/posts/post-with-code/img/10_demo2.gif differ diff --git a/docs/posts/post-with-code/img/11_demo3.gif b/docs/posts/post-with-code/img/11_demo3.gif new file mode 100644 index 0000000..259907d Binary files /dev/null and b/docs/posts/post-with-code/img/11_demo3.gif differ diff --git a/docs/posts/post-with-code/index.html b/docs/posts/post-with-code/index.html index 3985415..7a78a78 100644 --- a/docs/posts/post-with-code/index.html +++ b/docs/posts/post-with-code/index.html @@ -6,10 +6,10 @@ - - + + -Post With Code โ€“ blog +Pimping your {shiny} app with a JavaScript library : an example using sweetalert2 โ€“ Yohannโ€™s blog @@ -81,7 +115,7 @@