vue-r / vuer Goto Github PK
View Code? Open in Web Editor NEWvue.js for R
Home Page: https://vue-r.github.io/vueR
License: Other
vue.js for R
Home Page: https://vue-r.github.io/vueR
License: Other
As a shiny enthusiast, I am learning vue.
Thanks to vueR for letting us see the possibility of developing new shiny components based on shiny and vueR!
To make it easier to develop shiny components based on vueR, I think it is necessary to create new functions to enhance the ability of shiny and vue to communicate with each other
Ramnathv's vuer package https://github.com/ramnathv/vuer, inspired by the vueR, provides a set of functions to enhance shiny and vue's ability to communicate with each other:
Vue()
vueProxy()
vueUpdateData()
Unfortunately, this package has not been updated for five years.
Is it possible to incorporate his ideas into vueR?
In reactR
we have a helper / template for creating Shiny-based react
inputs. This article explains how to use the tools. I think providing similar tools in vueR
would be helpful for R developers to allow easy integration of vue
and abstract away some of the intimidating challenges of custom Shiny inputs.
reactable
has an underappreciated WidgetContainer
that handles htmlwidgets
(see lines ). I think vueR
should have a similar structure but without the tags
for data and options. Here is a very rough draft example that needs significant improvement, iteration, and testing.
library(htmltools)
library(htmlwidgets)
library(shiny)
library(vueR)
library(listviewer)
library(plotly)
# handle non-standard behaviors by some widgets
get_widget_data <- function(widget) {
as.tags(widget)[[2]]$children[[1]]
}
p <- plot_ly(palmerpenguins::penguins, x = ~bill_length_mm, y = ~body_mass_g)
tl <- tagList(
crosstalk::crosstalkLibs(), # necessary for g2
vueR::html_dependency_vue(minified = FALSE),
htmlDependency(
"htmlwidgets",
packageVersion("htmlwidgets"),
src = system.file("www", package = "htmlwidgets"),
script = "htmlwidgets.js"
),
p$dependencies, # this is far from ideal but plotly works differently; in most cases do not need to add since *Output handles
tags$div(
tags$button("update data", onclick = "updateData()")
),
tags$div(
id = "app",
tag('html-widget', list(
jsoneditOutput("je"),
`:x` = 'x',
`name` = 'jsonedit' # ideally we find a way to avoid this
)),
tag('html-widget', list(
plotlyOutput("pl"),
`:x` = 'x',
`name` = 'plotly' # ideally we find a way to avoid this
))
),
tags$script(HTML(
sprintf("
Vue.component(
'html-widget',
{
props: ['x', 'name'],
template: '<div><slot></slot></div>',
methods: {
// Copied from HTMLWidgets code
// Implement a vague facsimilie of jQuery's data method
elementData: function(el, name, value) {
if (arguments.length == 2) {
return el['htmlwidget_data_' + name];
} else if (arguments.length == 3) {
el['htmlwidget_data_' + name] = value;
return el;
} else {
throw new Error('Wrong number of arguments for elementData: ' +
arguments.length);
}
},
updateWidget: function() {
var component = this;
// use HTMLWidgets.widgets to give us a list of available htmlwidget bindings
var widgets = HTMLWidgets.widgets;
// assume there might be lots, so filter for the one we want
// in this case, we want jsonedit
var widget = widgets.filter(function(widget){
return widget.name === component.name
})[0];
// get our htmlwidget DOM element
var el = this.$el.querySelector('.html-widget');
var instance = this.elementData(el, 'init_result')
widget.renderValue(
el,
this.x,
instance
);
}
},
mounted: function() {
if(typeof(this.x) === 'undefined' || this.x === null) { return }
var component = this;
// use HTMLWidgets.widgets to give us a list of available htmlwiget bindings
var widgets = HTMLWidgets.widgets;
// assume there might be lots, so filter for the one we want
// in this case, we want jsonedit
var widget = widgets.filter(function(widget){
return widget.name === component.name
})[0];
// get our htmlwidget DOM element
var el = this.$el.querySelector('.html-widget');
// get our htmlwidget instance with initialize
var instance = widget.initialize(el);
this.elementData(el, 'init_result', instance);
widget.renderValue(
el,
this.x,
instance
);
},
// updated not working since does not watch deep
// but if the expectation is that data and options are replaced completely
// then updated will trigger
updated: function() {
this.updateWidget()
},
watch: {
x: {
handler: function() {console.log('updating');this.updateWidget()},
deep: true
}
}
}
)
var app = new Vue({
el: '#app',
data: () => (%s)
})
function updateData() {
app.x.data[0].y = app.x.data[0].y.map(d => Math.random())
}
",
get_widget_data(p)
)
))
)
browsable(tl)
I have always wondered with both vue
or mobx
+ Shiny
how our workflows/architecture might change if Shiny JavaScript state in Shiny.shinyapp.$inputValues
was reactive instead of a plain object. In earlier versions of JavaScript without proxy
, this idea is very limited in potential usage since added and deleted object properties are not tracked. However, with proxy
and the newest versions of mobx
and vue
, we can track added or effectively replace Shiny.shinyapp.$inputValues
with a reactive version early in the session llife and reap the full benefits of JavaScript reactivity fairly cleanly.
Shiny
proper would ever pursue a reactive JS
state since it would have to choose which reactive state engine it would use. Is there potential for Shiny
to make the choice based on active community and well-tested, stable JS dependencies for Shiny
to have a reactive JS engine as its input state? Which library would most likely meet the requirements? I would think choosing an existing library would be better than developing one from scratch.library(htmltools)
library(vueR)
library(shiny)
# experiment with standalone vue reactivity in bare page
# reference:
# https://vuejs.org/v2/guide/reactivity.html
# https://dev.to/jinjiang/understanding-reactivity-in-vue-3-0-1jni
browsable(
tagList(
tags$head(
tags$script(src = "https://unpkg.com/@vue/[email protected]/dist/reactivity.global.js"),
),
tags$p("we should see a number starting at 0 and increasing by one each second"),
tags$div(id = "reporter"),
tags$script(HTML(
"
let data = {x: 0};
let data_reactive = VueReactivity.reactive(data) // could also use ref for primitive value
console.log(data, data_reactive)
VueReactivity.effect(() => {
console.log(data_reactive.x)
document.getElementById('reporter').innerText = data_reactive.x
})
setInterval(function() {data_reactive.x++}, 1000)
"
))
)
)
# experiment with Shiny inputValues and vue-next
# reference:
# https://vuejs.org/v2/guide/reactivity.html
# https://dev.to/jinjiang/understanding-reactivity-in-vue-3-0-1jni
ui <- tagList(
tags$head(
tags$script(src = "https://unpkg.com/@vue/[email protected]/dist/reactivity.global.js"),
),
tags$div(
tags$h3("Increment with JavaScript"),
tags$span("Shiny: "),
textOutput("reporterR", inline = TRUE),
tags$span("JavaScript: "),
tags$span(
id = "reporterJS"
)
),
tags$div(
tags$h3("Increment with R/Shiny"),
tags$span("Shiny (used numeric input for convenience): "),
numericInput(inputId = 'x2', label = "", value = 0),
tags$span("JavaScript: "),
tags$span(
id = "reporterJS2"
)
),
tags$script(HTML(
"
$(document).on('shiny:connected', function() {
// once Shiny connected replace Shiny inputValues with reactive Shiny inputValues
Shiny.shinyapp.$inputValues = VueReactivity.reactive(Shiny.shinyapp.$inputValues)
// do our counter using Shiny.setInputValue from JavaScript
Shiny.setInputValue('x', 0) // initialize with 0
VueReactivity.effect(() => {
console.log('javascript', Shiny.shinyapp.$inputValues.x)
document.getElementById('reporterJS').innerText = Shiny.shinyapp.$inputValues.x
})
setInterval(
function() {
Shiny.setInputValue('x', Shiny.shinyapp.$inputValues.x + 1) //increment by 1
},
1000
)
// react to counter implemented in Shiny
VueReactivity.effect(() => {
console.log('shiny', Shiny.shinyapp.$inputValues['x2:shiny.number'])
document.getElementById('reporterJS2').innerText = Shiny.shinyapp.$inputValues['x2:shiny.number']
})
})
"
))
)
server <- function(input, output, session) {
x2 <- 0 # use this for state of Shiny counter
output$reporterR <- renderText({input$x})
observe({
invalidateLater(1000, session = session)
x2 <<- x2 + 1 # <<- or assign required to update parent
updateNumericInput(inputId = "x2", value = x2, session = session)
})
}
shinyApp(
ui = ui,
server = server,
options = list(launch.browser = rstudioapi::viewer)
)
I am getting the error:
package βvueRβ is not available (for R version 4.0.0)
whenever I try to install the package.
Is there a way around this?
Hi, sorry to bother @timelyportfolio but any attempt to configure vueR in order to make it working with the new vue 3 release ?
Best.
Fodil.
Playing around and here is a quick example. In reality, crosstalk can be subsumed by Vue
as simply a shared, reactive state mechanism.
library(vueR)
library(crosstalk)
library(htmltools)
data(mtcars)
sd <- SharedData$new(mtcars)
browsable(
tagList(
html_dependency_vue(minified = FALSE),
filter_select("gear", "Gears", sd, ~gear),
filter_slider("mpg", "Miles per gallon", sd, "mpg"),
filter_slider("cyl", "Cylinders", sd, "cyl"),
tags$div(
id = "app",
tags$h3("Filter Handle"),
"{{ filter.filteredKeys ? filter.filteredKeys : 'nothing filtered' }}",
tags$h3("Selection Handle"),
"{{ selection.value ? selection.value : 'nothing selected' }}"
),
tags$script(HTML(
sprintf(
"
const app = new Vue({
el: '#app',
data() {
return {
filter: new crosstalk.FilterHandle('%1$s'),
selection: new crosstalk.SelectionHandle('%1$s')
}
}
})
",
sd$groupName()
)
))
)
)
library(crosstalk)
library(plotly)
library(vueR)
library(htmltools)
tx <- highlight_key(txhousing)
widgets <- bscols(
widths = c(12, 12, 12),
filter_select("city", "Cities", tx, ~city),
filter_slider("sales", "Sales", tx, ~sales),
filter_checkbox("year", "Years", tx, ~year, inline = TRUE)
)
bc <- bscols(
widths = c(4, 8), widgets,
plot_ly(tx, x = ~date, y = ~median, showlegend = FALSE) %>%
add_lines(color = ~city, colors = "black")
)
browsable(
tagList(
html_dependency_vue(minified = FALSE),
bc,
tags$div(
id = "app",
tags$h3("Filter Handle"),
"{{ filter.filteredKeys ? filter.filteredKeys : 'nothing filtered' }}",
tags$h3("Selection Handle"),
"{{ selection.value ? selection.value : 'nothing selected' }}"
),
tags$script(HTML(
sprintf(
"
const app = new Vue({
el: '#app',
data() {
return {
filter: new crosstalk.FilterHandle('%1$s'),
selection: new crosstalk.SelectionHandle('%1$s')
}
}
})
",
tx$groupName()
)
))
)
)
library(crosstalk)
library(plotly)
library(vueR)
library(htmltools)
mh <- mtcars %>% highlight_key(~cyl)
pl <- mh %>%
plot_ly(
x = ~wt, y = ~mpg, text = ~cyl, mode = "markers+text",
textposition = "top", hoverinfo = "x+y"
) %>%
highlight(on = "plotly_hover", off = "plotly_doubleclick")
browsable(
tagList(
html_dependency_vue(minified = FALSE),
pl,
tags$div(
id = "app",
tags$h3("Filter Handle"),
"{{ filter.filteredKeys ? filter.filteredKeys : 'nothing filtered' }}",
tags$h3("Selection Handle"),
"{{ selection.value ? selection.value : 'nothing selected' }}"
),
tags$script(HTML(
sprintf(
"
const app = new Vue({
el: '#app',
data() {
return {
filter: new crosstalk.FilterHandle('%1$s'),
selection: new crosstalk.SelectionHandle('%1$s')
}
}
})
",
mh$groupName()
)
))
)
)
library(vueR)
library(crosstalk)
library(htmltools)
data(mtcars)
sd <- SharedData$new(mtcars)
browsable(
tagList(
html_dependency_vue(minified = FALSE),
filter_slider("mpg", "Miles per gallon", sd, "mpg"),
filter_slider("cyl", "Cylinders", sd, "cyl"),
tags$div(
id = "app",
"{{ $data.get()._value ? $data.get()._value : 'nothing filtered' }}"
),
tags$script(HTML(
sprintf(
"
const app = new Vue({
el: '#app',
data() {
return crosstalk.group('%s').var('filterset')
}
})
",
sd$groupName()
)
))
)
)
Thanks for a great package. Having fun trying some of your examples but when I try to render as a Rmd file to html I get this error. Eg:
mint <- htmlDependency(
name = "mint-ui",
version = "2.0.5",
src = c(href="https://unpkg.com/mint-ui/lib"),
script = "index.js",
stylesheet = "style.css"
)
rmarkdown:::validate_html_dependency(mint)
#Error: path for html_dependency not provided
This has likely nothing to do with this package and is about rmarkdown/knitr, but if you could point me in the right direction to get the dependencies working for Rmd I'd appreciate it!
Michael
I often wonder why does Shiny manage UI state from R instead of UI state (especially state that does not affect/require R) being managed in JavaScript. If we pursue this approach with vuex
then JavaScript would manage all UI/frontend state and call R only when necessary allowing JS/HTML/CSS to do what it does best and more directly. This decision does not require vuex
, but in a vue
context I think vuex
is a good established solution.
# quick example of Vuex and Shiny
# reference: https://vuex.vuejs.org/guide/
library(htmltools)
library(shiny)
library(vueR)
vuex <- tags$script(src="https://unpkg.com/[email protected]/dist/vuex.js")
ui <- tagList(
html_dependency_vue(offline = FALSE, minified = FALSE),
tags$head(vuex),
tags$h1("Shiny with Vue and Vuex"),
tags$h3('Vue App'),
tags$div(
id = "app",
tags$button(`@click`="increment", "+"),
tags$button(`@click`="decrement", "-"),
"{{ count }}"
),
tags$h3("Shiny controls"),
# add Shiny buttons to demonstrate Shiny to update Vuex state
actionButton(inputId = "btnIncrement", label="+"),
actionButton(inputId = "btnDecrement", label="-"),
tags$script(HTML(
"
// first example in Vuex documentation
Vue.use(Vuex)
// make sure to call Vue.use(Vuex) if using a module system
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
})
const app = new Vue({
el: '#app',
store: store,
computed: {
count () {
return this.$store.state.count
}
},
methods: {
increment () {
this.$store.commit('increment')
},
decrement () {
this.$store.commit('decrement')
}
}
})
$(document).on('shiny:sessioninitialized', function() {
// increment from Shiny custom message
// chose 'increment' but does not have to match the store mutation name
Shiny.addCustomMessageHandler('increment', function(msg) {
app.$store.commit('increment')
});
Shiny.addCustomMessageHandler('decrement', function(msg) {
app.$store.commit('decrement')
});
})
"
))
)
#browsable(ui)
server <- function(input, output, session) {
observeEvent(input$btnIncrement, {
session$sendCustomMessage("increment", list())
})
observeEvent(input$btnDecrement, {
session$sendCustomMessage("decrement", list())
})
}
shinyApp(
ui = ui,
server = server,
options = list(launch.browser = rstudioapi::viewer)
)
Thanks for this great package. How can one link a reactive vueR variable to select topojson properties in leaflet? I tried the following example. The preselection incidents
is working, but leaflet doesn't rerendering when I select an other property. Any Idea, how to fix that?
library(htmltools)
library(leaflet) # I installed leaflet 1.0 with devtools::install_github("timelyportfolio/[email protected]")
library(leaflet.extras)
library(vueR)
topojson <- readr::read_file('https://rawgit.com/TrantorM/leaflet-choropleth/gh-pages/examples/basic_topo/crimes_by_district.topojson')
map <- leaflet() %>%
setView(-75.14, 40, zoom = 11) %>%
addProviderTiles("CartoDB.Positron") %>%
addGeoJSONChoropleth(topojson,valueProperty = "{{selected}}")
ui <- tagList(tags$div(id="app",
tags$select("v-model" = "selected",
tags$option("disabled value"="","Select one"),
tags$option("incidents"),
tags$option("dist_num")),
tags$span("Selected: {{selected}}"),
tags$div(map)
),
tags$script(
"
var app = new Vue({
el: '#app',
data: {
selected: 'incidents'
}
});
"
),
html_dependency_vue())
browsable(ui)
is it possible to use vueR to reproduce this example?
https://www.raymondcamden.com/2018/04/19/an-example-of-the-async-clipboard-api-with-vuejs
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.