Animate plots using echarts4r when quarto slides are on-screen - r
I am making a Revealjs presentation using Quarto in R Studio. I am using the package {echarts4r} to make my plots. {echarts4r} comes with default animations. When I render the presentation the default animation has already loaded for all the slides.
I want to run the default echarts4r animations when the slide is active (i.e. when the slide is in view) and reset when some other slide is in view. Could someone help me with this?
Here is the code for the quarto presentation.
---
title: "A Title"
subtitle: "A Subtitle"
author: "First Last"
institute: "Some Institute"
date: today
self-contained: true
format: revealjs
---
## Introduction
Hello There!
## Pie Chart
```{r}
library(tidyverse)
library(echarts4r)
data <- tibble(name = c("A", "B", "C", "D", "E", "F", "G"),
number = c(9.7, 2.1, 2.1, 1.9, 1.9, 1.9, 80.4))
data %>%
e_charts(name) %>%
e_pie(number, radius = c("50%", "70%")) %>%
e_legend(orient = "vertical", right = "5", top = "65%")
```
This isn't a fix or a Quarto method. However, this workaround is dynamic. I worked out what needs to happen with both echarts4r and highcharter because of the comment by #bretauv.
Assign Element IDs
The only thing you'll change with your charts is that you need to give them an element ID. IDs need to be unique across the entire presentation. It's perfectly okay to use something like 'ec0', 'ec1'... and so on for your plots' ids. (You only need to do this to plots that animate.)
Here's an example.
```{r pltEchart, echo=F}
iris |> group_by(Species) |>
e_charts(Sepal.Length, elementId = "pltEcht") |> # <--- id is here
e_scatter(Sepal.Width)
```
For highcharter, and most other widget style packages, adding the element id isn't built in. Here's how you add the id for highcharter.
```{r penquins,echo=F}
hc <- hchart(penguins, "scatter",
hcaes(x = flipper_length_mm, y = bill_length_mm, group = species))
hc$elementId <- "hc_id"
hc
```
Re-Animating Plots
For each of echarts4r plots, to apply this next part you need the slide it's on and it's element id.
For highcharter, you also need to know the order in which it appears in your presentation (only in terms of other highcharter plots).
Whether you use the less dynamic approach or the more dynamic approach, what remains is adding a JS chunk to your QMD. This chunk can go anywhere in the script file. (I usually put any JS in my RMDs/QMDs at the end.)
If you were not aware, JS is built-in. You don't need to do anything new or different to use JS this way. However, if you were to run this chunk in the source pane, it won't do anything. You have to render it to see it in action. If you end up changing the JS or writing more of your own, don't assume what you see in RStudio's viewer or presentation pane is exactly what you'll see in your browser.
I would skim over what you have to change for both methods before you decide! If you're not comfortable using JS, the second is definitely your best bet.
Customized Information For Each Presentation For Each Re-Animated Plot
For each plot you re-animate, you'll have to identify:
a substring of the slide title OR the slide number (where slide numbers are included on the slides via YAML declaration)
plot element ids
plot order (plot order or sequence is for Highcharts only).
For the slide title substring, as you use it in the JS:
it's a substring of the title based on the hash or anchor that is assigned in the background
substring is all lowercase
no special characters
must be unique to that slide
If you're unsure what to use or how to find what's unique—there's an easy way to find that information. If you open your presentation in your browser from the presentation pane in RStudio, the URL of the slides will look similar to this.
Every slide will have the same initial component to the URL, http://localhost:7287/#/, but beyond that, every slide will be unique.
http://localhost:7287/#/another-hc
The string after the # is the title anchor for that slide. You can use exactly what's in the URL (after #/).
Put it Altogether
This continuously checks if the slide has changed (10-millisecond interval). If there's a slide change, it then checks to see if one of the three plots is on that slide. If so, the slide animation is restarted.
What you need to personalize in the JS
ecReloader for charts4r has 2 arguments:
title substring OR slide number
plot element ID
hcReloader for highcharter has 3 arguments:
title substring OR slide number
plot element ID
plot sequence number
In the setInterval function (chunk named customizeMe, you will need to write a function call for each plot you want to re-animate. In my example, I reanimated three plots. This is the ONLY part you modify. (Note that each line needs to end with a semi-colon.)
ecReloader('code', 'pltEcht'); /* slide title. plot element id */
hcReloader('highchart', 'hc_id', 0); /* slide title, id, sequence */
hcReloader(6, 'another_hc_id', 1); /* slide number, id, sequence */
/* assuming only one on slide */
setInterval(function() {
var current = window.location.hash;
tellMe = keepLooking(current);
if(tellMe) { /* if the slide changed, then look */
ecReloader('code', 'pltEcht');
hcReloader('highchart', 'hc_id', 0);
hcReloader(6, 'another_hc_id', 1); /* second highcharter plot */
}
}, 10); // check every 10 milliseconds
In your presentation, you need to take both of the JS chunks to make this work. (Chunk names are customizeMe and reloaders.)
I'm sure there's a way to customize the appearance of the slide numbers; this code is based on the default, though.
Here's all the JS to make this work.
```{r customizeMe,echo=F,engine='js'}
/* assuming only one on slide */
setInterval(function() {
var current = window.location.hash;
tellMe = keepLooking(current);
if(tellMe) { /* if the slide changed, then look */
ecReloader('code', 'pltEcht');
hcReloader('highchart', 'hc_id', 0);
hcReloader(6, 'another_hc_id', 1); /* second highcharter plot */
}
}, 10); // check every 10 milliseconds
```
```{r reloaders,echo=F,engine='js'}
// more dynamic; a couple of key words for each plot
// multiple options for addressing Echarts plots
function ecReloader(slide, id) {
/* slide (string) slide title unique substring (check URL when on the slide)
--or--
(integer) as in the slide number
id (string) element id of the plot to change */
if(typeof slide === 'number') { // slide number provided
which = document.querySelector('div.slide-number'); // page numbers like '6 / 10'
validator = Number(which.innerText.split(' ')[0]);
if(slide === validator) { // slide number matches current slide
var ec = document.getElementById(id);
ele = echarts.init(ec, get_e_charts_opts(ec.id));
thatsIt = get_e_charts_opts(ec.id); /* store data */
ele.setOption({xAxis: {}, yAxis: {}}, true); /* remove data */
ele.setOption(thatsIt, false); /* append original data */
}
} else { // unique element in slide title
if(window.location.hash.indexOf(slide) > -1) {
var ec = document.getElementById(id);
ele = echarts.init(ec, get_e_charts_opts(ec.id));
thatsIt = get_e_charts_opts(ec.id); /* store data */
ele.setOption({xAxis: {}, yAxis: {}}, true); /* remove data */
ele.setOption(thatsIt, false); /* append original data */
}
}
}
// multiple options for addressing Highcharts plots, assumes 1 chart per slide!
function hcReloader(slide, id, order) {
/* slide (string) slide title unique substring (check URL when on the slide)
--or--
(integer) as in the slide number
id (string) element id of the plot to change
order (integer) 0 through the number of charts in the plot, which one is this plot?
(in order of appearance) */
if(typeof slide === 'number') { // slide number provided
which = document.querySelector('div.slide-number'); // page numbers like '6 / 10'
validator = Number(which.innerText.split(' ')[0]);
if(slide === validator) { // slide number matches current slide
var hc1 = document.getElementById(id).firstChild;
Highcharts.chart(hc1, Highcharts.charts[order].options); // re-draw plot
}
} else { // unique element in slide title
if(window.location.hash.indexOf(slide) > -1) {
var hc1 = document.getElementById(id).firstChild;
Highcharts.chart(hc1, Highcharts.charts[order].options); // re-draw plot
}
}
}
/* Current Slide Section (bookmark #) */
oHash = window.location.hash;
/* check if the slide has changed */
function keepLooking (nHash) {
if(oHash === nHash) {
return false;
} else {
oHash = nHash; /* if slide changed, reset the value of oHash */
return true;
}
}
```
Here is the entire QMD script I used to create and test this so you can see how it works.
---
title: "Untitled"
format:
revealjs:
slide-number: true
editor: source
---
## Quarto
```{r basics, echo=F}
library(echarts4r)
library(tidyverse)
library(htmltools)
library(highcharter)
```
```{r data, include=F,echo=F}
data("iris")
data(penguins, package = "palmerpenguins")
```
word
## Bullets
more words
## More Plots; How about Highcharter?
```{r penquins,echo=F}
hc <- hchart(penguins, "scatter",
hcaes(x = flipper_length_mm, y = bill_length_mm, group = species))
hc$elementId <- "hc_id"
hc
```
## Code
`echarts` style plot
```{r pltEcht, echo=F}
iris |> group_by(Species) |>
e_charts(Sepal.Length, elementId = "pltEcht") |> e_scatter(Sepal.Width)
```
## Another HC
```{r penquins2,echo=F}
hc2 <- hchart(iris, "scatter",
hcaes(x = Sepal.Length, y = Sepal.Width, group = Species))
hc2$elementId <- "another_hc_id"
hc2
```
```{r customizeMe,echo=F,engine='js'}
/* assuming only one on slide */
setInterval(function() {
var current = window.location.hash;
tellMe = keepLooking(current);
if(tellMe) { /* if the slide changed, then look */
ecReloader('code', 'pltEcht');
hcReloader('highchart', 'hc_id', 0);
hcReloader(6, 'another_hc_id', 1); /* second highcharter plot */
}
}, 10); // check every 10 milliseconds
```
```{r reloaders,echo=F,engine='js'}
// more dynamic; a couple of key words for each plot
// multiple options for addressing Echarts plots
function ecReloader(slide, id) {
/* slide (string) slide title unique substring (check URL when on the slide)
--or--
(integer) as in the slide number
id (string) element id of the plot to change */
if(typeof slide === 'number') { // slide number provided
which = document.querySelector('div.slide-number'); // page numbers like '6 / 10'
validator = Number(which.innerText.split(' ')[0]);
if(slide === validator) { // slide number matches current slide
var ec = document.getElementById(id);
ele = echarts.init(ec, get_e_charts_opts(ec.id));
thatsIt = get_e_charts_opts(ec.id); /* store data */
ele.setOption({xAxis: {}, yAxis: {}}, true); /* remove data */
ele.setOption(thatsIt, false); /* append original data */
}
} else { // unique element in slide title
if(window.location.hash.indexOf(slide) > -1) {
var ec = document.getElementById(id);
ele = echarts.init(ec, get_e_charts_opts(ec.id));
thatsIt = get_e_charts_opts(ec.id); /* store data */
ele.setOption({xAxis: {}, yAxis: {}}, true); /* remove data */
ele.setOption(thatsIt, false); /* append original data */
}
}
}
// multiple options for addressing Highcharts plots, assumes 1 chart per slide!
function hcReloader(slide, id, order) {
/* slide (string) slide title unique substring (check URL when on the slide)
--or--
(integer) as in the slide number
id (string) element id of the plot to change
order (integer) 0 through the number of charts in the plot, which one is this plot?
(in order of appearance) */
if(typeof slide === 'number') { // slide number provided
which = document.querySelector('div.slide-number'); // page numbers like '6 / 10'
validator = Number(which.innerText.split(' ')[0]);
if(slide === validator) { // slide number matches current slide
var hc1 = document.getElementById(id).firstChild;
Highcharts.chart(hc1, Highcharts.charts[order].options); // re-draw plot
}
} else { // unique element in slide title
if(window.location.hash.indexOf(slide) > -1) {
var hc1 = document.getElementById(id).firstChild;
Highcharts.chart(hc1, Highcharts.charts[order].options); // re-draw plot
}
}
}
/* Current Slide Section (bookmark #) */
oHash = window.location.hash;
/* check if the slide has changed */
function keepLooking (nHash) {
if(oHash === nHash) {
return false;
} else {
oHash = nHash; /* if slide changed, reset the value of oHash */
return true;
}
}
```
You can put the JS anywhere in your QMD.
If you see delays in loading (flashing, that sort of thing), you can lower the milliseconds between intervals. (That number is at the end of the setInterval function, where you see }, 10).
If something goes wrong, you can just set the JS to eval=F. You didn't actually change anything in your presentation permanently.
Related
CSS selector for font color in flextable with shadow host on
I'm unsuccessfully trying to set the font color for flextable generated in r markdown using a css stylesheet. I can accomplish this when I turn off shadow host, but not with it on. (Just turning it off removes other desirable features.) Here's a short r markdown file demonstrating the difference. --- title: "Untitled" output: html_document --- <style> div.flextable-shadow-host * { color: pink; } div.tabwid * { color: pink; } </style> # ignores CSS above ```{r, echo=FALSE} library(flextable) flextable(head(mtcars)) ``` # accepts CSS above ```{r, echo=FALSE} ft <- flextable(head(mtcars)) htmltools_value(ft, ft.shadow = FALSE) ``` I want the css external to the r code because I have a button selector on the website the user can change the overall style (e.g., dark mode or not).
When using shadow, the table is assembled outside of HTML. Only the id connects the table to HTML. However, flextable has functions for setting the color. Why not just use one of the many built-in methods to change the color? For example: # ignores CSS above ```{r liberator,include=F} library(flextable) library(tidyverse) ``` ```{r tbler, echo=FALSE} flextable(head(mtcars)) %>% color(color = "pink", part = "all") ``` # accepts CSS above ```{r, echo=FALSE} ft <- flextable(head(mtcars)) htmltools_value(ft, ft.shadow = FALSE) ``` There are many things you can do with flextable styling. You can see more customization options here. Update: Based on your comments Okay, this works to change the color of a flextable. This works if there is only one flextable in the script. I have the color of the text set to #b21E29 (a shade of red). You can change that as you see fit. These will SKIP non-shadow flextables Add this chunk anywhere in your RMD script. This requires no additional libraries or any other customization in your R code. ```{r js_ing,results="asis",engine="js",echo=F} // extract the styles that are set for the flextable letMe = document.querySelector('div.flextable-shadow-host').shadowRoot.querySelector('div>style'); // replace color style // preceding ';' so that 'background-color' doesn't change letMe.innerHTML = letMe.innerHTML.replace(/;(color:.+?);/g, ';color:#b21e29 !important;'); ``` If you have more than one flextable with shadow on, you can use one of the two following chunks instead. In the first--all the same color; in the second--each table has a different color. These work if there is more than one flextable in the script. Pay attention to the comments so you can see what to use when depending on your desired output. All the same color: ```{r moreJs_ing,results="asis",engine="js",echo=F} // collect all of the flextables with shadow letMe = document.querySelectorAll('div.flextable-shadow-host'); // to set all shadow flextables to the same font color: for(i = 0, n = letMe.length; i < n; i++){ showMe = letMe[i].shadowRoot.querySelector('div>style'); showMe.innerHTML = showMe.innerHTML.replace(/;(color:.+?);/g, ';color:#b21e29 !important;'); } ``` Each with there own color: ```{r evenMoreJs_ing,results="asis",engine="js",echo=F} //alternatively to set each to a different color // make sure you only include one of these options! // collect all of the flextables with shadow letMe = document.querySelectorAll('div.flextable-shadow-host'); // first table in script showFirst = letMe[0].shadowRoot.querySelector('div>style'); showFirst.innerHTML = showFirst.innerHTML.replace(/;(color:.+?);/g, ';color:#b21e29 !important;'); // second table in script showSecond = letMe[1].shadowRoot.querySelector('div>style'); showSecond.innerHTML = showSecond.innerHTML.replace(/;(color:.+?);/g, ';color:#003b70 !important;'); // change the indices for each table, keep in mind the first table is [0], not [1] ``` If you aren't sure where you want to go with these, add all three and and include=F as a chunk option to the two you aren't using at that moment in time.
PaperJS, Need to select items underneath a transparent raster using Mouse Down
I have a Canvas with multiple raster images. I use onMouseDown on Tool to find select the item which was clicked. I have a new requirement. Suppose, two images overlap each other, and the upper image is partially transparent. That makes the lower image visible. But when I try to click on the lower image, obviously I end up choosing the upper image. Failed Attempt I tried to use the getPixel(point) function on Raster. I thought if I can figure that the selected pixel is transparent, I can ignore that raster and look for other items. But I am not getting the color value that I am expecting (transparent or not) using this function. So, my second thought was that I need to change the mousedown event point from the global co-ordinate space to local raster co-ordinate space. It still did not work. Is there a way to achieve what I want? Code tool.onMouseDown = (event) => { project.activeLayer.children.forEach((item) => { if (item.contains(event.point)) { // check if hit was on a transparent raster pixel const pixel = item.getPixel(event.point) console.error(pixel.toCSS(true)) // 2nd attempt const pixel = item.getPixel(item.globalToLocal(event.point)) console.error(pixel.toCSS(true)) } } }
There is a simpler way to do what you want to achieve. You can rely on project.hitTestAll() method to do a hit test on all items. Then, if the hit item is a raster, hit pixel color information will be contained in hitResult.color. hitResult.color.alpha is all you need to check if a raster was hit on a non-transparent pixel. Here is a sketch demonstration of the solution. const dataUrl = 'data:image/png;base64,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'; const lowOpacity = 0.3; // create 2 rasters new Raster({ source: dataUrl, opacity: lowOpacity, onLoad: function() { this.position = view.center - 100; } }); new Raster({ source: dataUrl, opacity: lowOpacity, onLoad: function() { this.position = view.center + 100; } }); // on mouse down function onMouseDown(event) { // unselect previously selected items paper.project.selectedItems.forEach(item => { item.selected = false; item.opacity = lowOpacity; }); // do a hit test on all project items const hitResults = project.hitTestAll(event.point); // for each hit result for (let i = 0; i < hitResults.length; i++) { const hitResult = hitResults[i]; // if item was hit on a non transparent pixel if (hitResult && hitResult.color && hitResult.color.alpha > 0) { // select item hitResult.item.selected = true; hitResult.item.opacity = 1; // break loop break; } } }
Overlapping Label in IcCube Reporting
I have a problem with overlapping labels in a bar charts. The settings in the Axis label appear to be correct, because others settings do affect the rendering (bold, etc..) but spacing does not seem to be taken into account. Any way to do a proper spacing of the labels, or actually to rotate them ? Update1: After applying the proposed settings this is what I get: The labels are rotated but do not fit the widget. I tried updating the margins, without a positive result.
There are no options in the widget fields but you can rotate these labels using the "On Widget Options" hook. You can find it on "Hooks" tab. "On Widget Options" hook use this function: function(context, options, $box) { for (var i = 0; i < options.categoryAxis.guides.length; i++) { options.categoryAxis.guides[i].labelRotation = 30; // rotation angle } return options; } Available fields for Guides can be checked here Custom code Result UPDATE: You can change top margin using this code lines: options.marginTop = 88; // Top margin Just add it into "On Widget Options" hook: function(context, options, $box) { options.marginTop = 88; // Top margin for (var i = 0; i < options.categoryAxis.guides.length; i++) { options.categoryAxis.guides[i].labelRotation = 30; } return options; }
My own defined colors for graphs in Kintone
I'd like to set my own defined colors for graphs that appear in Kintone. I've found out for pie charts, you can upload the below CSS code to the App to have some areas of the pie to become a color of your choice. .highcharts-series-group .highcharts-series path:nth-of-type(even){ fill:pink; } What I'd really like to do though, is apply the same thing to the Line charts in kintone. I've tried the below CSS: .highcharts-tracker path { fill: red; } This only changes the points plotted on the graph, but not the lines in between the points. How can I identify the lines in this graph so that I can end up with lines of the color of my choice??
Updated 6/24/18 Like you mentioned, the code that I showed you displays only on the record detail page. However, if you just make the process to run on the record list event "app.record.index.show", you can show the graph on the top of the record list page. Also, it will be better to use kintone.app.getHeaderSpaceElement() to append a graph on the record list page. The following page is an example of how to append something on the record list page using the kintone.app.getHeaderSpaceElement(): kintone developer network - kintone x OpenStreetMap https://developer.kintone.io/hc/en-us/articles/115003669514 The following page is about the record list header element: kintone developer network - Get Record List Header Element https://developer.kintone.io/hc/en-us/articles/213148937-Get-Record-List#getHeaderSpaceElement ================================================= Original Reply It's better off not editing the DOM because it might not work after any kintone updates. I recommend creating a custom graph using Chart.js, a javscript library. The following page helps you how to do so. Example Code (function() { "use strict"; // Events for adding and editing records var eventsCreateShow = ['app.record.create.show', 'app.record.edit.show', 'app.record.index.create.show', 'app.record.index.edit.show']; kintone.events.on(eventsCreateShow, function(event) { // Hide the "Chart" Group field kintone.app.record.setFieldShown('Chart', false); }); // Display the chart on the record details page (PC and mobile) var eventsDetailShow = ['app.record.detail.show', 'mobile.app.record.detail.show']; kintone.events.on(eventsDetailShow, function(event) { var record = event.record; var data = { labels: ["Language Arts", "Math", "Science", "Social Studies", "P.E."], datasets: [ { label: "My First dataset", fillColor: "rgba(0,140,232,.4)", strokeColor: "rgba(151,187,205,1)", pointColor: "rgba(151,187,205,1)", pointStrokeColor: "#fff", data: [ record['language_arts']['value'], record['math']['value'], record['science']['value'], record['social_studies']['value'], record['pe']['value'] ] } ] }; // Set Chart.js options var options = { scaleShowLine: true, angleShowLineOut: true, scaleShowLabels: true, scaleBeginAtZero: true, angleLineColor: "rgba(0,0,0,.1)", angleLineWidth: 1, pointLabelFontFamily: "'Arial'", pointLabelFontStyle: "normal", pointLabelFontSize: 16, pointLabelFontColor: "#666", pointDot: true, pointDotRadius: 5, pointDotStrokeWidth: 1, pointHitDetectionRadius: 20, datasetStroke: true, datasetStrokeWidth: 3, datasetFill: true, responsive: true }; var elRadar; var elCanvas = document.createElement('canvas'); elCanvas.id = 'canvas'; // Display radar chart onto the Blank space // Edit display size depending on PC or mobile if (event.type === 'mobile.app.record.detail.show') { elRadar = kintone.mobile.app.getHeaderSpaceElement(); elCanvas.style.position = 'relative'; elCanvas.style.top = '10px'; elCanvas.style.left = '10px'; elCanvas.height = '300'; elCanvas.width = '300'; } else { elRadar = kintone.app.record.getSpaceElement('Radar'); elCanvas.height = '400'; elCanvas.width = '400'; } elRadar.appendChild(elCanvas); var myChart = new Chart(elCanvas.getContext("2d")).Radar(data, options); }); })(); Ref:kintone developer network - Display radar charts with chart.js https://developer.kintone.io/hc/en-us/articles/115006413388-Display-radar-charts-with-chart-js I hope this helps
Rmarkdown: Indentation of TOC items in HTML output
I want to indent TOC according to header level. My example document looks like this: # Tutorial ## Start a new project ### Project structure ### Analysis code I'm compiling Rmd document with: rmarkdown::render("foo.Rmd", output_options = HTMLlook, output_file = "foo.html") HTMLlook <- list(toc = TRUE, toc_depth = 5, toc_float = list(collapsed = FALSE, smooth_scroll = TRUE)) This produces document with TOC However, I want indented TOC (indentation equivalent to header level). Wanted result should look like this: Is it possible to set this option in render or maybe pass css parameters to it?
I am not aware of a built-in solution. But here is a little tweak: <script> $(document).ready(function() { $items = $('div#TOC li'); $items.each(function(idx) { num_ul = $(this).parentsUntil('#TOC').length; $(this).css({'text-indent': num_ul * 10, 'padding-left': 0}); }); }); </script> The depth of your headers is actually mapped inside the TOC. For each level you go down, a new ul element is created. This is what we are making use of here. In detail: When the document has finished loading ($(document).ready(....): Select all list items inside the element with id TOC For each list item count the number of parent elements until you reach the element with id TOC. This is the number of ul elements. Change the style for the current list item according to the number of parents. You can tweak the spacing by playing around with the two parameters for text-indent and padding-left. MRE: --- title: "Habits" author: Martin Schmelzer date: September 14, 2017 output: html_document: toc: true toc_depth: 5 toc_float: collapsed: false smooth_scroll: true --- <script> $(document).ready(function() { $items = $('div#TOC li'); $items.each(function(idx) { num_ul = $(this).parentsUntil('#TOC').length; $(this).css({'text-indent': num_ul * 10, 'padding-left': 0}); }); }); </script> # In the morning ## Waking up ### Getting up #### Take a shower ##### Make coffee # In the evening ## Make dinner This is the result: