Closed jonesworks closed 2 years ago
I want this feature in the package too but have not found a great way to bring that in yet. The issue is not so much technical but in creating a nice API.
Best I can do for now is something like below. This works but it feels like a bad API to me.
mtcars2 <- mtcars |>
head() |>
tibble::rownames_to_column("model")
e1 <- mtcars2 |>
e_charts(model) |>
e_bar(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e2 <- mtcars2 |>
e_charts(model) |>
e_pie(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
cb <- "() => {
let x = 0;
setInterval(() => {
x++
chart.setOption(opts[x % 2], true);
}, 3000);
}"
e_morph(e1, e2, callback = cb)
Note: I just added e_morph
so you'll need the Github version to make this work.
Awesome! Thank you so much!
Maybe @rdatasculptor has some idea on how this could be improved
@JohnCoene
First of all: This is very exciting! Second: I can't seem to find documentation on the morph feature on the echarts main site. Do you know where I can find it? Edit: found it!
Third: though I think this is extremely exciting and eye candy :), I have a hard time finding real life purposes. For me it would be way more interesting if I (as a user) could control manually which charts is being shown, meaning: being able to switch manually between e.g. a pie and a bar chart using some kind of buttons. Also I am aware of the fact that this would be an additional feature compared to the echarts js functionality (?).
Fourth: regarding the API, my first thought is to separate the callback object into more than one argument to pass e_morph function. But I need think this over...
On the third bit, it's doable. Instead of rotating through we can have it triggered by a button.
mtcars2 <- mtcars |>
head() |>
tibble::rownames_to_column("model")
e1 <- mtcars2 |>
e_charts(model) |>
e_bar(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e2 <- mtcars2 |>
e_charts(model) |>
e_pie(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
cb <- "() => {
let x = 0;
document.getElementById('toggle')
.addEventListener('click', (e) => {
x++
chart.setOption(opts[x % 2], true);
});
}"
e_morph(e1, e2, callback = cb) %>%
htmlwidgets::prependContent(
htmltools::tags$button("Toggle", id = "toggle")
)
Since it's all JavaScript we can do whatever we like on the front-end really.
For the API we can then think of convenience functions to build up those toggles, as well as convenience functions for Shiny (where buttons, etc. is more intuitive to most R users), passing a JavaScript function as a string is horrendous.
Brilliant work! This very cool, I will try out some ideas as soon as possible
@JohnCoene One idea could be to make telling a data story easier: use the transition between charts to highlight parts of the chart and tell the user why these parts are important. I know this can be done by using the timeline functionality, but e_morph() seems much easier to use for this goal.
Conceptually something like this:
mtcars2 <- mtcars |>
head() |>
tibble::rownames_to_column("model")
max <- list(
name = "Max",
type = "max"
)
e1 <- mtcars2 |>
e_charts(model) |>
e_bar(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e2 <- mtcars2 |>
e_charts(model) |>
e_title("And here is the interesting part of the chart. Take a look at this bar") |>
e_grid(top = 100) |>
e_legend(right = 10) |>
e_bar(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
) |>
e_mark_point("carb", data = max)
e3 <- mtcars2 |>
e_charts(model) |>
e_title("And here is the interesting part of the chart. Take a look at this bar") |>
e_grid(top = 100) |>
e_legend(right = 10) |>
e_pie(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
cb <- "() => {
let x = 0;
document.getElementById('toggle')
.addEventListener('click', (e) => {
x++
chart.setOption(opts[x % 3], true);
});
}"
e_morph(e1, e2, e3, callback = cb) %>%
htmlwidgets::prependContent(
htmltools::tags$button("Toggle", id = "toggle")
)
I noticed it is currently not yet possible to add more than two charts to the e_morph() function (so there isn't much of a data story in this example), but the idea should be clear.
In this perspective I think it would be nice to be able to give each chart it's own button. Or to make a next and previous button, or something like that.
I'll take a look later, thank you. You should be able to pass as many charts as you want to e_morph
, it uses ...
I think it might be good for the scrollytell type? Maybe @z3tt has some ideas?
I added a third chart to my conceptual example. As you can see, the third one is not shown as one of the charts in the final transition.
Yes because the JavaScript is unchanged, need to change the modulo x % 2
to x % 3
Yes... that was an easy one. Sorry for having bothered you with this one!
Okay, this is perhaps a better example of a "scrollytell" type? The content is worthless though. But I guess you get the point :)
---
title: "Scrollytelling"
output:
flexdashboard::flex_dashboard:
self_contained: true
vertical_layout: fill
theme: lumen
---
```{r, echo=FALSE, comment=FALSE, warning=FALSE}
library(echarts4r)
library(tidyverse)
df <- Titanic
mtcars2 <- mtcars %>%
#head() %>%
tibble::rownames_to_column("model")
max <- list(
name = "Max",
type = "max"
)
min <- list(
name = "Min",
type = "min"
)
e01 <- mtcars2 %>% slice(1:2) %>%
e_charts(model) %>%
e_title("Look at this nice circle,\ndevided into two parts.\n\nBut what is it?", top = 40) %>%
e_grid(top = 100) %>%
e_x_axis(show = FALSE) %>%
e_legend(show = FALSE, right = 10) %>%
e_pie(
qsec, label = list(show = FALSE),
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e1 <- mtcars2 %>% slice(1:2) %>%
e_charts(model) %>%
e_title("Pie chart alert!\n\nThese are 'qsec' scores of two cars\nBut okay, two categories... We will keep the pie...\n\nBut what if we add a car?\nIs the pie still readable...?", top = 40) %>%
e_grid(top = 100) %>%
e_legend(right = 10) %>%
e_pie(
qsec,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e2 <- mtcars2 %>% slice(1:3) %>%
e_charts(model) %>%
e_title("Well I don't think so.\n\nI guess it all gets even worse if we add another car", top = 40) %>%
e_grid(top = 100) %>%
e_legend(right = 10) %>%
e_pie(
qsec,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e3 <- mtcars2 %>% slice(1:4) %>%
e_charts(model) %>%
e_title("Yes... here you go :(.\n\nFor most people the pie fails to be easy to read.\n\nLet's turn it into a rose. Is that better?", top = 40) %>%
e_grid(top = 100) %>%
e_legend(right = 10) %>%
e_pie(
qsec,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e4 <- mtcars2 %>% slice(1:4) %>%
e_charts(model) %>%
e_title("Yes, the rose type chart is way better to understand :).\n\nBut I am curious about how it looks like in a line chart.", top = 40) %>%
e_grid(top = 100) %>%
e_legend(right = 10) %>%
e_pie(
qsec,
roseType = TRUE,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e5 <- mtcars2 %>% slice(1:6) %>%
e_charts(model) %>%
e_title("Here's the line chart!\n\nNow the silly thing is that we connect cars by lines\nwhilst there is no connection at all\nthey are still categories.\n\nlet's turn it into a bar chart", top = 40) %>%
e_grid(top = 100) %>%
e_legend(right = 10) %>%
e_line(
qsec,
universalTransition = TRUE,
animationDurationUpdate = 1000L
) %>% e_y_axis(show = FALSE)
e6 <- mtcars2 %>% slice(1:6) %>%
e_charts(model) %>%
e_title("yes, that's better!\n\nWe lost the colors though\n\n(ofcourse that can be done properly in echarts4,\nbut today I am lazy)", top = 40) %>%
e_grid(top = 100) %>%
e_legend(right = 10) %>%
e_bar(
qsec,
universalTransition = TRUE,
animationDurationUpdate = 1000L
) %>% e_y_axis(show = FALSE)
e7 <- mtcars2 %>% slice(1:6) %>%
e_charts(model) %>%
e_title("By the way, here are the cars with\nlowest and highest scores.\n\nLet's add more cars!\nI am wondering which ones will be the lowest and hightest", top = 40) %>%
e_grid(top = 100) %>%
e_legend(right = 10) %>%
e_bar(
qsec,
universalTransition = TRUE,
animationDurationUpdate = 1000L
) %>%
e_y_axis(show = FALSE) %>%
e_mark_point(data = max) %>%
e_mark_point(data = min)
e8 <- mtcars2 %>% slice(1:20) %>%
e_charts(model) %>%
e_title("Well.. for me this a nice final chart of this data story :)\n\nbye all!", top = 40) %>%
e_grid(top = 200, bottom = 100) %>%
e_legend(right = 10) %>%
e_bar(
qsec,
universalTransition = TRUE,
animationDurationUpdate = 1000L
) %>%
e_x_axis(axisLabel = list(rotate = 30)) %>%
e_y_axis(show = FALSE) %>%
e_mark_point(data = max) %>%
e_mark_point(data = min)
cb <- "() => {
let x = 0;
document.getElementById('toggle')
.addEventListener('click', (e) => {
x++
chart.setOption(opts[x % 9], true);
});
}"
e_morph(e01, e1, e2, e3,e4, e5, e6,e7,e8,callback = cb) %>%
htmlwidgets::prependContent(
htmltools::tags$button("Continue telling!", id = "toggle")
)
@JohnCoene if you have a short amount of time and if you please ofcourse :), would you be able to provide an example of giving every chart in the e_morph() sequence its own (radio)button? (A radio button seems appropriate, since switching to another chart means deselection of the former chart).
I did some experiments in combining timeline with e_morph(). I think it is a powerful combination to be able to both switch between subselections of the data and add (if needed by the user) extra information or proper morphs to each subselection. A button for each added-information 'morph' would make it more logical.
For me e_morph is another feature of echarts4r that makes it possible to not always needing to use shiny ,,🎉 when the data and the dashboard get complex.
You can do something like this.
library(echarts4r)
library(htmltools)
mtcars2 <- mtcars |>
head() |>
tibble::rownames_to_column("model")
e1 <- mtcars2 |>
e_charts(model) |>
e_bar(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e2 <- mtcars2 |>
e_charts(model) |>
e_pie(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
cb <- "() => {
let x = 0;
let elements = document.getElementsByClassName('echarts-input');
Array.from(elements).forEach(function(element) {
element.addEventListener('change', (e) => {
chart.setOption(opts[e.target.value -1 ], true);
});
});
}"
e_morph(e1, e2, callback = cb) %>%
htmlwidgets::prependContent(
tags$div(
class = "form-check",
tags$input(
class = "form-check-input echarts-input",
type = "radio",
name = "echarts",
checked = NA,
value = "1"
),
tags$label(
class = "form-check-label",
"First chart"
)
),
tags$div(
class = "form-check",
tags$input(
class = "form-check-input echarts-input",
type = "radio",
name = "echarts",
value = "2"
),
tags$label(
class = "form-check-label",
"Second chart"
)
)
)
Thank you very much for your quick response and your efforts! This is exactly what I meant. this is so cool! very, very inspring.
(another note to myself: learn javascript...)
@JohnCoene Hopefully one last question. Do you think it is possible to include the radiobuttons somewhere in the echarts area? Now they push the chart partially off the screen. Maybe by putting the radiobuttons together with the chart in an extra div, or something like that. I will try some ideas
I would just use some CSS.
e_morph(e1, e2, callback = cb) %>%
htmlwidgets::prependContent(
div(
style = "position:absolute;top: 1rem;right: 1rem;",
tags$div(
class = "form-check",
tags$input(
class = "form-check-input echarts-input",
type = "radio",
name = "echarts",
checked = NA,
value = "1"
),
tags$label(
class = "form-check-label",
"First chart"
)
),
tags$div(
class = "form-check",
tags$input(
class = "form-check-input echarts-input",
type = "radio",
name = "echarts",
value = "2"
),
tags$label(
class = "form-check-label",
"Second chart"
)
)
)
)
thanks! now the radio buttons don't respond andymore. but I get the idea.
playing around with e_grid did the trick as well, but that, ofcourse is an ugly solution.
Ah yes, change the z-index
e_morph(e1, e2, callback = cb) %>%
htmlwidgets::prependContent(
div(
style = "position:absolute;top: 1rem;right: 1rem;z-index: 999;",
tags$div(
class = "form-check",
tags$input(
class = "form-check-input echarts-input",
type = "radio",
name = "echarts",
checked = NA,
value = "1"
),
tags$label(
class = "form-check-label",
"First chart"
)
),
tags$div(
class = "form-check",
tags$input(
class = "form-check-input echarts-input",
type = "radio",
name = "echarts",
value = "2"
),
tags$label(
class = "form-check-label",
"Second chart"
)
)
)
)
@JohnCoene you rule!
Wow, what a great new feature! Not much to add here, the example by @rdatasculptor gives a nice overview of what can be possible. Can't wait to play around with it!
@JohnCoene just a question. Though it really works great (!), It's a little bit tricky to combine a transitioned chart with other charts using a combineWidgets()
function of the manipulateWidgets package. A proper placement of the radiobuttons is hard. Is there a way to let the radiobuttons or transition buttons be a part of the chart area themselves, like the legend or timeline? I reckon that feature should be part of the echarts package intead of echarts4r?
@rdatasculptor late reply again, was away on holiday last week.
It's definitely not part of echarts.js, it can be done with standard HTML. Do you have a small example for me to look at?
@JohnCoene No problem at all! I think you really well deserved this holiday :)
A small example
library(echarts4r)
library(htmltools)
library(manipulateWidget)
mtcars2 <- mtcars |>
head() |>
tibble::rownames_to_column("model")
e1 <- mtcars2 |>
e_charts(model) |>
e_bar(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
e2 <- mtcars2 |>
e_charts(model) |>
e_pie(
carb,
universalTransition = TRUE,
animationDurationUpdate = 1000L
)
cb <- "() => {
let x = 0;
let elements = document.getElementsByClassName('echarts-input');
Array.from(elements).forEach(function(element) {
element.addEventListener('change', (e) => {
chart.setOption(opts[e.target.value -1 ], true);
});
});
}"
plot1 <- e_morph(e1, e2, callback = cb)
df <- data.frame(
x = seq(50),
y = rnorm(50, 10, 3),
z = rnorm(50, 11, 2),
w = rnorm(50, 9, 2)
)
plot2 <- df |>
e_charts(x) |>
e_line(z) |>
e_area(w) |>
e_title("Line and area charts")
combineWidgets(plot1, plot2, nrow = 1, colsize = c(1, 2)) %>%
htmlwidgets::prependContent(
div(
style = "position:absolute;top: 1rem;right: 1rem;z-index: 999;",
tags$div(
class = "form-check",
tags$input(
class = "form-check-input echarts-input",
type = "radio",
name = "echarts",
checked = NA,
value = "1"
),
tags$label(
class = "form-check-label",
"First chart"
)
),
tags$div(
class = "form-check",
tags$input(
class = "form-check-input echarts-input",
type = "radio",
name = "echarts",
value = "2"
),
tags$label(
class = "form-check-label",
"Second chart"
)
)
)
)
The radiobuttons, obviously, appear next to the second chart instead of next to the morphing chart. It works, and I guess I can play around with relocating the radiobuttons. But I was wondering if it could be possible to add the buttons automatically in the morphing chart area, with the buttons placed relatively from the chart area and not from the entire html page.
Hi John,
I love your work! Thank you. A lot. The libraries you've put together are fantastic. I've also started reading your recent book. I hope to create a widget for RoughViz.js -- a first effort of sorts...
I just read 'What's New in Apache ECharts 5.20'. Transitioning from plot type to plot type (from bar to pie, from scatter to bar, etc.) is all the rage. It's done, if I understand correctly, by way of Universal Transition.
I don't believe the question's been asked here, nor has it been asked on SO (my apologies if it has). Is there a somewhat straightforward way to implement this functionality?
Below is code from an example in Echarts docs (Morphing Transitions Across Series is the example's heading). Here is a link to relevant documentation: https://echarts.apache.org/handbook/en/basics/release-note/5-2-0/
Thanks!