diff --git a/CHANGELOG.md b/CHANGELOG.md index 4df6056f..0691bbd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.9.0] - 2020-10-31 + +### Fixed +- Fixes a minor bug in `setCallbackContext` (described in [#236](https://github.com/plotly/dashR/issues/236)) which prevented pattern-matching callbacks from working properly if one or more `input` statements did not include a selector. [#237](https://github.com/plotly/dashR/pull/237) + +### Changed + +- Dash for R now depends on v1.1.1 of `dashHtmlComponents` +- Dash for R now depends on v1.13.0 of `dashCoreComponents` +- Dash for R now depends on v4.11.0 of `dashTable` +- `dash-renderer` version is now v1.8.3 + ## [0.8.0] - 2020-10-27 ### Fixed - Usage of `glue` has been corrected to address [#232](https://github.com/plotly/dashR/issues/232) via [#233](https://github.com/plotly/dashR/pull/233). diff --git a/DESCRIPTION b/DESCRIPTION index 7c32031c..dfabe490 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,14 +1,14 @@ Package: dash Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications -Version: 0.8.0 +Version: 0.9.0 Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plotly.com"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plotly.com"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person("Hammad", "Khan", role = c("aut"), comment = c(ORCID = "0000-0003-2479-9841"), email = "hammadkhan@plotly.com"), person(family = "Plotly Technologies", role = "cph")) Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required. Depends: R (>= 3.0.2) Imports: - dashHtmlComponents (== 1.0.3), - dashCoreComponents (== 1.10.2), - dashTable (== 4.9.0), + dashHtmlComponents (== 1.1.1), + dashCoreComponents (== 1.13.0), + dashTable (== 4.11.0), R6, fiery (> 1.0.0), routr (> 0.2.0), @@ -33,9 +33,9 @@ Collate: 'imports.R' 'print.R' 'internal.R' -Remotes: plotly/dash-html-components@e63acfa, - plotly/dash-core-components@0770afb, - plotly/dash-table@75ac3d9 +Remotes: plotly/dash-html-components@7209e0a, + plotly/dash-core-components@91a424e, + plotly/dash-table@aa519b7 License: MIT + file LICENSE Encoding: UTF-8 LazyData: true diff --git a/R/internal.R b/R/internal.R index 113e5733..41079910 100644 --- a/R/internal.R +++ b/R/internal.R @@ -36,9 +36,9 @@ all_files = FALSE), class = "html_dependency"), `dash-renderer-dev` = structure(list(name = "dash-renderer", - version = "1.8.2", - src = list(href = "https://unpkg.com/dash-renderer@1.8.2", - file = "lib/dash-renderer@1.8.2"), + version = "1.8.3", + src = list(href = "https://unpkg.com/dash-renderer@1.8.3", + file = "lib/dash-renderer@1.8.3"), meta = NULL, script = "dash-renderer/dash_renderer.dev.js", stylesheet = NULL, @@ -48,9 +48,9 @@ all_files = FALSE), class = "html_dependency"), `dash-renderer-map-dev` = structure(list(name = "dash-renderer", - version = "1.8.2", - src = list(href = "https://unpkg.com/dash-renderer@1.8.2", - file = "lib/dash-renderer@1.8.2"), + version = "1.8.3", + src = list(href = "https://unpkg.com/dash-renderer@1.8.3", + file = "lib/dash-renderer@1.8.3"), meta = NULL, script = "dash-renderer/dash_renderer.dev.js.map", stylesheet = NULL, @@ -60,9 +60,9 @@ all_files = FALSE), class = "html_dependency"), `dash-renderer-prod` = structure(list(name = "dash-renderer", - version = "1.8.2", - src = list(href = "https://unpkg.com/dash-renderer@1.8.2", - file = "lib/dash-renderer@1.8.2"), + version = "1.8.3", + src = list(href = "https://unpkg.com/dash-renderer@1.8.3", + file = "lib/dash-renderer@1.8.3"), meta = NULL, script = "dash-renderer/dash_renderer.min.js", stylesheet = NULL, @@ -72,9 +72,9 @@ all_files = FALSE), class = "html_dependency"), `dash-renderer-map-prod` = structure(list(name = "dash-renderer", - version = "1.8.2", - src = list(href = "https://unpkg.com/dash-renderer@1.8.2", - file = "lib/dash-renderer@1.8.2"), + version = "1.8.3", + src = list(href = "https://unpkg.com/dash-renderer@1.8.3", + file = "lib/dash-renderer@1.8.3"), meta = NULL, script = "dash-renderer/dash_renderer.min.js.map", stylesheet = NULL, diff --git a/R/utils.R b/R/utils.R index 3715e7d8..70892f35 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1054,7 +1054,7 @@ setCallbackContext <- function(callback_elements) { function(x) { input_id <- splitIdProp(x)[1] prop <- splitIdProp(x)[2] - + # The following conditionals check whether the callback is a pattern-matching callback and if it has been triggered. if (startsWith(input_id, "{")){ id_match <- vapply(callback_elements$inputs, function(x) { @@ -1062,18 +1062,22 @@ setCallbackContext <- function(callback_elements) { any(x[grepl("id.", names(x))] %in% jsonlite::fromJSON(input_id)[[1]]) }, logical(1))[[1]] } else { - id_match <- vapply(callback_elements$inputs, function(x) x$id %in% input_id, logical(1)) + id_match <- vapply(callback_elements$inputs, function(x) { + unlist(x) + any(x$id %in% input_id)}, logical(1)) } - + if (startsWith(input_id, "{")){ prop_match <- vapply(callback_elements$inputs, function(x) { x <- unlist(x) any(x[names(x) == "property"] %in% prop) }, logical(1))[[1]] } else { - prop_match <- vapply(callback_elements$inputs, function(x) x$property %in% prop, logical(1)) + prop_match <- vapply(callback_elements$inputs, function(x) { + unlist(x) + any(x$property %in% prop)}, logical(1)) } - + if (startsWith(input_id, "{")){ if (length(callback_elements$inputs) == 1 || !is.null(unlist(callback_elements$inputs, recursive = F)$value)) { value <- sapply(callback_elements$inputs[id_match & prop_match], `[[`, "value") diff --git a/inst/lib/dash-renderer@1.8.2/dash-renderer/dash_renderer.dev.js b/inst/lib/dash-renderer@1.8.3/dash-renderer/dash_renderer.dev.js similarity index 100% rename from inst/lib/dash-renderer@1.8.2/dash-renderer/dash_renderer.dev.js rename to inst/lib/dash-renderer@1.8.3/dash-renderer/dash_renderer.dev.js diff --git a/inst/lib/dash-renderer@1.8.2/dash-renderer/dash_renderer.dev.js.map b/inst/lib/dash-renderer@1.8.3/dash-renderer/dash_renderer.dev.js.map similarity index 100% rename from inst/lib/dash-renderer@1.8.2/dash-renderer/dash_renderer.dev.js.map rename to inst/lib/dash-renderer@1.8.3/dash-renderer/dash_renderer.dev.js.map diff --git a/inst/lib/dash-renderer@1.8.2/dash-renderer/dash_renderer.min.js b/inst/lib/dash-renderer@1.8.3/dash-renderer/dash_renderer.min.js similarity index 100% rename from inst/lib/dash-renderer@1.8.2/dash-renderer/dash_renderer.min.js rename to inst/lib/dash-renderer@1.8.3/dash-renderer/dash_renderer.min.js diff --git a/inst/lib/dash-renderer@1.8.2/dash-renderer/dash_renderer.min.js.map b/inst/lib/dash-renderer@1.8.3/dash-renderer/dash_renderer.min.js.map similarity index 100% rename from inst/lib/dash-renderer@1.8.2/dash-renderer/dash_renderer.min.js.map rename to inst/lib/dash-renderer@1.8.3/dash-renderer/dash_renderer.min.js.map diff --git a/tests/integration/callbacks/test_pattern_matching.py b/tests/integration/callbacks/test_pattern_matching.py index c7cc26a3..c481c1c4 100644 --- a/tests/integration/callbacks/test_pattern_matching.py +++ b/tests/integration/callbacks/test_pattern_matching.py @@ -318,6 +318,120 @@ """ +graphs_app = """ +library(dash) +library(dashHtmlComponents) +library(dashCoreComponents) +library(plotly) + +df <- read.csv( + file = "https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv", + stringsAsFactor=FALSE, + check.names=FALSE +) + +app <- Dash$new() + +app$layout( + htmlDiv( + list( + htmlDiv( + list( + dccDropdown(options = lapply(unique(df[,"country"]), function(x) { + list(label = x, value = x) + }), + value = "Canada", + id = "country", + style = list(display = "inline-block", + width = 200) + ), + htmlButton( + "add Chart", + id = "add-chart", + n_clicks = 0, + style = list(display = "inline-block") + ) + ) + ), + htmlDiv(id = "container", children=list()), + htmlDiv(id = "output-delay") + ) + ) +) + +create_figure <- function(df, column_x, column_y, country) { + df <- df[which(df[, "country"] == country),] + if (column_x == "year") { + fig <- plot_ly(df, x = df[,column_x], y = df[,column_y], name = column_x, type = "scatter", mode = "lines") + } else { + fig <- plot_ly(df, x = df[,column_x], y = df[,column_y], name = column_x, type = "scatter", mode = "markers") + } + fig <- plotly::layout(fig, plot_bgcolor="lightblue", xaxis = list(title=""), + yaxis = list(title=""), title=list(text=paste(country, column_y, "vs", column_x), + xanchor="right", margin_l=10, margin_r=0, margin_b=30)) + return(fig) +} + +app$callback( + output = list( + output(id = "container", property = "children"), + output(id = "output-delay", property = "children") + ), + params = list( + input(id = "add-chart", property = "n_clicks"), + state(id = "country", property = "value"), + state(id = "container", property = "children") + ), + function(n_clicks, country, children) { + default_column_x <- "year" + default_column_y <- "gdpPercap" + + new_element <- htmlDiv( + style = list(width = "23%", display = "inline-block", outline = "thin lightgrey solid", padding = 10), + children = list( + dccGraph( + id = list(type = "dynamic-output", index = n_clicks), + style = list(height = 300), + figure = create_figure(df, default_column_x, default_column_y, country) + ), + dccDropdown( + id = list(type = "dynamic-dropdown-x", index = n_clicks), + options = lapply(colnames(df), function(x) { + list(label = x, value = x) + }), + value = default_column_x + ), + dccDropdown( + id = list(type = "dynamic-dropdown-y", index = n_clicks), + options = lapply(colnames(df), function(x) { + list(label = x, value = x) + }), + value = default_column_y + ) + ) + ) + + children <- c(children, list(new_element)) + return(list(children, n_clicks)) + } +) + +app$callback( + output(id = list("index" = MATCH, "type" = "dynamic-output"), property = "figure"), + params = list( + input(id = list("index" = MATCH, "type" = "dynamic-dropdown-x"), property = "value"), + input(id = list("index" = MATCH, "type" = "dynamic-dropdown-y"), property = "value"), + input(id = "country", property = "value") + ), + function(column_x, column_y, country) { + return(create_figure(df, column_x, column_y, country)) + } +) + +app$run_server() +""" + + def test_rpmc001_pattern_matching_all(dashr): dashr.start_server(all_app) dashr.find_element("#add-filter").click() @@ -370,3 +484,16 @@ def test_rpmc004_pattern_matching_todo(dashr): dashr.find_element("#add").click() dashr.find_element('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"done\\"\\}').click() assert dashr.wait_for_text_to_equal("#totals", "1 of 1 items completed - 100%") + + +def test_rpmc005_pattern_matching_graphs(dashr): + dashr.start_server(graphs_app) + dashr.select_dcc_dropdown("#country", "Cameroon") + dashr.wait_for_text_to_equal("#output-delay", "0") + dashr.find_element("#add-chart").click() + dashr.wait_for_text_to_equal("#output-delay", "1") + dashr.find_element('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"dynamic-output\\"\\}') + dashr.select_dcc_dropdown('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"dynamic-dropdown-x\\"\\}', "year") + dashr.select_dcc_dropdown('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"dynamic-dropdown-y\\"\\}', "pop") + dashr.percy_snapshot("r-pmc-graphs") + dashr.wait_for_element('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"dynamic-output\\"\\}')