Changing line thickness and opacity in scatterplot on onRender() htmlWidgets in R - r

I am hoping to make a plot using the R package htmlwidgets' onRender() function in which a user can click on a point and a line is drawn. I have the crux of it working right now where a gray line is drawn at its default thickness and probably its default opaqueness.
However, I have been stuck on changing the thickness of the line (and possibly changing the opaqueness of the line, although it may be working and I cannot see it since the line is so thin). I want the line to be very thick and rather transparent. I tried several parameters and approaches for line width and opaqueness (some of which are commented out below), but it seems they do not make a difference. Any ideas what I may be missing? Thank you.
library(plotly)
library(broom)
dat <- mtcars
dat$mpg <- dat$mpg * 10
p <- ggplot(data = dat, aes(x=disp,y=mpg)) + geom_point(size=0.5)
ggplotly(p) %>%
onRender("
function(el, x, data) {
// reduce the opacity of every trace except for the hover one
el.on('plotly_click', function(e) {
var trace1 = {
x: [100, 400],
y: [100, 400],
mode: 'lines',
//line: dict(color: 'gray', width: 100)
marker: {
color: 'gray',
size: 200,
width: 1000,
opacity: 0.5
}
}
Plotly.addTraces(el.id, trace1);
})
}
", data=dat)

The opacity would need to be in the trace object. Your line object has some syntax issues which prevents Javascript from reading it.
gp %>% onRender("
function(el, x, data) {
el.on('plotly_click', function(e) {
var trace1 = {
x: [100, 400],
y: [100, 400],
mode: 'lines',
line: {
color: 'gray',
width: 100
},
opacity: 0.8,
}
Plotly.addTraces(el.id, trace1);
})
}", data=dat)

Related

I want to show only categories in the legend of a highchart treemap. Is there a way to do that using shiny?

I want to make a treemap to get an impression of the sizes in one data.frame. Let me show you an example:
I use the following code to produce this chart:
library(shiny)
library(highcharter)
library(gapminder)
library(dplyr)
ui <- fluidPage(
highchartOutput("hcontainer")
)
server <- function(input, output){
output$hcontainer <- renderHighchart({
gapminder %>%
filter(year == 2007) %>%
data_to_hierarchical(group_vars = c(continent, country),
size_var = pop,
colors = c('pink','yellow','blue','green','orange','red')) %>%
hchart(type = "treemap"
#showInLegend = TRUE,
#legendType='point',
)
})
}
shinyApp(ui,
server,
options = list(launch.browser = TRUE)
)
I saw it is possible to create a legend by uncommenting those two lines of code in the hchart-function, but the result is not what I want:
Is there a way to tell highcharts that I only want the continents in my legend?
As a less important sidenote: There seems to be a bug in the highcharts, because after clicking on the legend (which you can use to hide/make reappear countries), they change their color in the legend according to their continent:
Use the following plugin to achieve that:
(function(H) {
let pick = H.pick,
defined = H.defined,
fireEvent = H.fireEvent
H.wrap(H.Legend.prototype.getAllItems = function(p) {
var allItemsFirst = [],
allItems = [];
this.chart.series.forEach(function(series) {
var seriesOptions = series && series.options;
// Handle showInLegend. If the series is linked to another series,
// defaults to false.
if (series && pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? void 0 : false, true)) {
// Use points or series for the legend item depending on
// legendType
allItemsFirst = allItems.concat(series.legendItems ||
(seriesOptions.legendType === 'point' ?
series.data :
series));
}
});
allItemsFirst.forEach(el => {
if (el.isVisibleInLegend) {
allItems.push(el)
}
})
fireEvent(this, 'afterGetAllItems', {
allItems: allItems
});
return allItems;
});
}(Highcharts));
And set isVisibleInLegend: true for each point from you expect to be in the legend.
Highcharts.chart('container', {
series: [{
type: "treemap",
layoutAlgorithm: 'squarified',
showInLegend: true,
legendType: 'point',
data: [{
id: "id_1",
name: 'A',
isVisibleInLegend: true
}, {
id: "id_2",
name: 'A1',
value: 2,
parent: 'id_1',
}, {
id: "id_3",
name: 'A2',
value: 2,
parent: 'id_1',
}]
}]
});
JS Demo:
https://jsfiddle.net/BlackLabel/vdqtok9m/
Extending Highcharts:
https://www.highcharts.com/docs/extending-highcharts/extending-highcharts

Is it possible to change borderColor for area bands (e_band2) in echarts4r?

Goal:
In my plot, I would like to have different colors for the border and the area of an area band. The plot was created with echarts4r using the e_band2() function.
Problem:
In the documentation I read that area bands can be customized through the itemStyle argument of the function. This works fine for all the other options (borderWidth, borderType, shadowBlur, shadowColor) I tested, but not for borderColor. However, the borderColor option seems to work at least partially, because the symbol in the legend has the desired border color, but not the area band in the plot. Does anyone know if there is another way to change the border color or is this a bug?
Reprex:
library(echarts4r)
library(dplyr)
data(EuStockMarkets)
as.data.frame(EuStockMarkets) |>
dplyr::slice_head(n = 200) |>
dplyr::mutate(day = 1:dplyr::n()) |>
e_charts(day) |>
e_band2(DAX, SMI, itemStyle = list(
borderWidth = 1,
color = "green",
borderColor = "red"
)) |>
e_y_axis(scale = TRUE)
For some reason, it's hardcoded to ignore your style settings for the stroke color (in SVG, that's the lines) by echarts4r.
However, you can change it back.
I've taken the code from the echarts4r package for the JS function renderBand and modified it to use the settings that are defined in the plot call (which is what you wanted and expected).
Note that I've made this a string.
renderBands2 <- "
function renderBands2(params, api) {
if (params.context.rendered) return;
params.context.rendered = true;
/* set polygon vertices */
let points = [];
let i = 0;
while (typeof api.value(0,i) != 'undefined' && !isNaN(api.value(0,i))) {
points.push(api.coord([api.value(0,i), api.value(1,i)])); /* lo */
i++;
}
for (var k = i-1; k > -1 ; k--) {
points.push(api.coord([api.value(0,k), api.value(2,k)])); /* up */
}
return {
type: 'polygon',
shape: {
points: echarts.graphic.clipPointsByRect(points, {
x: params.coordSys.x, y: params.coordSys.y,
width: params.coordSys.width, height: params.coordSys.height
})}, style: api.style()
};
}"
Apply this to your plot with htmlwidgets::onRender. This calls for the plot to be re-rendered with this new function for the data series.
as.data.frame(EuStockMarkets) |>
dplyr::slice_head(n = 200) |>
dplyr::mutate(day = 1:dplyr::n()) |>
e_charts(day) |>
e_band2(DAX, SMI, itemStyle = list(
borderWidth = 1, color = "green", borderColor = "red")) |>
e_y_axis(scale = TRUE) %>%
htmlwidgets::onRender(paste0(
"function(el, data) {
", renderBands2,
"
chart = get_e_charts(el.id);
opts = chart.getOption();
opts.series[0].renderItem = renderBands2;
chart.setOption(opts, true); /* all better! */
}"))

inner radius polar Highcharter

I'm trying to change the inner radius of a polar chart using highcharter so I can visualize the data by hoving the tooltip like in this awesome D3 charts from fivethirtyeight.
I know that it's possible to visualize data with solid gauge like in this example, but I want to the data to be visible in a polar.
I've tried changing innerSize and innerRadius parameters but I'm not able to accomplish it.
Here's my R code:
library(tidyverse)
library(highcharter)
highchart() %>%
hc_chart(polar = T, type = "bar") %>%
hc_title(text = "Athlete 1 vs Athlete 2") %>%
hc_xAxis(categories = c("Total Score", "Avg. Score", "Sum Score",
"Best Score"),
tickmarkPlacement = "on",
plotLines = list(
list(label = list(
rotation = 90))
)
) %>%
hc_yAxis(min = 0) %>%
hc_series(
list(
name = "Athlete 1",
data = c(43000, 19000, 60000, 35000)
),
list(
name = "Athlete 2",
data = c(50000, 39000, 42000, 31000)
)
) %>%
hc_colors(c("firebrick", "steelblue"))
The desired output would be something like:
Thank you!
EDIT
After #ppotaczek's answer I've updated with his chart, so the desired updated would look like this:
To achieve similar result in Highcharts polar chart, you should set pointPadding and groupPadding to 0. To create an empty space in the middle of the graph you can use Highcharts.SVGRenderer and offset for yAxis.
Highcharts.chart('container', {
chart: {
polar: true,
type: 'bar',
events: {
render: function() {
var chart = this,
middleElement = chart.middleElement;
if (middleElement) {
middleElement.destroy();
}
chart.middleElement = chart.renderer.circle(chart.plotSizeX / 2 + chart.plotLeft, chart.plotHeight / 2 + chart.plotTop, 20).attr({
zIndex: 3,
fill: '#ffffff'
}).add();
}
}
},
yAxis: {
offset: 20
},
series: [{
pointPadding: 0,
groupPadding: 0,
name: "Athlete 1",
data: [43000, 19000, 60000, 35000]
}, {
pointPadding: 0,
groupPadding: 0,
name: "Athlete 2",
data: [50000, 39000, 42000, 31000]
}]
});
Live demo: http://jsfiddle.net/BlackLabel/y6uL180j/
API Reference: https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#circle
EDIT:
To display value from hovered point in the middle of the chart, use tooltip with proper options:
tooltip: {
borderWidth: 0,
backgroundColor: 'none',
shadow: false,
style: {
fontSize: '16px'
},
headerFormat: '',
pointFormatter: function() {
return this.y / 1000 + 'k'
},
positioner: function(labelWidth, labelHeight) {
return {
x: (this.chart.plotSizeX - labelWidth) / 2 + this.chart.plotLeft,
y: (this.chart.plotSizeY - labelHeight) / 2 + this.chart.plotTop
};
}
}
Live demo: http://jsfiddle.net/BlackLabel/5ybhtrmz/

Shiny: How to change the shape and/or size of the clicked point?

I would like to change the shape and size of the clicked point in the below plot. How to achieve it? For this toy plot, I have reduced the number of points from original 100k to 2k. So, the expected solution should be highly scalable and do not deviate from the original plot i.e., all the colors before and after the update of the click point should be the same.
library(shiny)
library(plotly)
df <- data.frame(X=runif(2000,0,2), Y=runif(2000,0,20),
Type=c(rep(c('Type1','Type2'),600),
rep(c('Type3','Type4'),400)),
Val=sample(LETTERS,2000,replace=TRUE))
# table(df$Type, df$Val)
ui <- fluidPage(
title = 'Select experiment',
sidebarLayout(
sidebarPanel(
checkboxGroupInput("SelType", "Select Types to plot:",
choices = unique(df$Type),
selected = NA)
),
mainPanel(
plotlyOutput("plot", width = "400px"),
verbatimTextOutput("click")
)
)
)
server <- function(input, output, session) {
output$plot <- renderPlotly({
if(length(input$SelType) != 0){
df <- subset(df, Type %in% input$SelType)
p <- ggplot(df, aes(X, Y, col = as.factor(Val))) +
geom_point()
}else{
p <- ggplot(df, aes(X, Y, col = as.factor(Val))) +
geom_point()
}
ggplotly(p) %>% layout(height = 800, width = 800)
})
output$click <- renderPrint({
d <- event_data("plotly_click")
if (is.null(d)) "Click events appear here (double-click to clear)"
else cat("Selected point associated with value: ", d$Val)
})
}
shinyApp(ui, server)
A related question has been asked here, but that approach of highlighting the point with a color does not work(when the number of levels of a variable is high, it is difficult to hard code a color which might be already present in the plot).
Plotly's restyle function won't help us here but we can still use the onclick event together with a little bit of JavaScript. The code has acceptable performance for 10,000 points.
We can get the point which was clicked on in JavaScript using:
var point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[data.points[0].curveNumber].getElementsByClassName('point')[data.points[0].pointNumber];
(scatterlayer is the layer where all the scatterplot elements are located,
scatter[n] is the n-th scatter plot and point[p] is the p-th point in it)
Now we just make this point a lot bigger (or whatever other shape/transformation you want):
point.setAttribute('d', 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z');
In order to get the possibility to revert everything, we store the unaltered info about the point together with the rest of the Plotly information:
var plotly_div = document.getElementsByClassName('plotly')[0];
plotly_div.backup = {curveNumber: data.points[0].curveNumber,
pointNumber: data.points[0].pointNumber,
d: point.attributes['d'].value
}
and later we can restore the point:
var old_point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[plotly_div.backup.curveNumber].getElementsByClassName('point')[plotly_div.backup.pointNumber]
old_point.setAttribute('d', plotly_div.backup.d);
Now we can add all the code to the plotly widget.
javascript <- "
function(el, x){
el.on('plotly_click', function(data) {
var point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[data.points[0].curveNumber].getElementsByClassName('point')[data.points[0].pointNumber];
var plotly_div = document.getElementsByClassName('plotly')[0];
if (plotly_div.backup !== undefined) {
var old_point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[plotly_div.backup.curveNumber].getElementsByClassName('point')[plotly_div.backup.pointNumber]
if (old_point !== undefined) {
old_point.setAttribute('d', plotly_div.backup.d);
}
}
plotly_div.backup = {curveNumber: data.points[0].curveNumber,
pointNumber: data.points[0].pointNumber,
d: point.attributes['d'].value,
style: point.attributes['style'].value
}
point.setAttribute('d', 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z');
});
}"
[...]
ggplotly(p) %>% onRender(javascript)
Alternatively you could make a new SVG element based on the location of the clicked point but in the color and shape you would like.
You can try it here without R/Shiny.
//create some random data
var data = [];
for (var i = 0; i < 10; i += 1) {
data.push({x: [],
y: [],
mode: 'markers',
type: 'scatter'});
for (var p = 0; p < 200; p += 1) {
data[i].x.push(Math.random());
data[i].y.push(Math.random());
}
}
//create the plot
var myDiv = document.getElementById('myDiv');
Plotly.newPlot(myDiv, data, layout = { hovermode:'closest'});
//add the same click event as the snippet above
myDiv.on('plotly_click', function(data) {
//let's check if some traces are hidden
var traces = document.getElementsByClassName('legend')[0].getElementsByClassName('traces');
var realCurveNumber = data.points[0].curveNumber;
for (var i = 0; i < data.points[0].curveNumber; i += 1) {
if (traces[i].style['opacity'] < 1) {
realCurveNumber -= 1
}
}
data.points[0].curveNumber = realCurveNumber;
var point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[data.points[0].curveNumber].getElementsByClassName('point')[data.points[0].pointNumber];
var plotly_div = document.getElementsByClassName('plotly')[0];
if (plotly_div.backup !== undefined) {
var old_point = document.getElementsByClassName('scatterlayer')[0].getElementsByClassName('scatter')[plotly_div.backup.curveNumber].getElementsByClassName('point')[plotly_div.backup.pointNumber]
if (old_point !== undefined) {
old_point.setAttribute('d', plotly_div.backup.d);
}
}
plotly_div.backup = {curveNumber: data.points[0].curveNumber,
pointNumber: data.points[0].pointNumber,
d: point.attributes['d'].value,
style: point.attributes['style'].value
}
point.setAttribute('d', 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z');
});
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="myDiv">

How to change format values in a Pie Chart made by plotly (in R)

First of all thank you for spending a little bit of your time helping me to solve this issue.
I am getting started in plot_ly through R and I am struggling when trying to change the format of the values of my Pie Chart (I want them to be showed in the plot as currency "$" format).
So far my code looks like:
data <- data.frame(Level = c("Receipt","Disbursement"),Amount = c(1000,2000))
name_dataset <- "Overview"
plot_ly(data=data, labels = Level, values = Amount, type = "pie", textinfo= "label+percent",
hoverinfo = "label+percent+value", outsidetextfont = list(color = "white")) %>% layout(title = paste0(paste(unlist(strsplit(name_dataset,"_")),collapse = " ")))
Thank you for your help!
Are you looking for something like this:
data$AmountB <- prettyNum(data$Amount, big.mark=",",scientific=FALSE) #EDIT
data$AmountB <- paste(data$AmountB, "$", sep="")
plot_ly(data=data, labels = Level, values = Amount, type = "pie", textinfo= "text", text=AmountB,hoverinfo = "text", outsidetextfont = list(color = "white")) %>%
layout(title = paste0(paste(unlist(strsplit(name_dataset,"_")),collapse = " ")))
var text = [15588, 16787, 27778].map(function (v, i) {
return currencyFormatterForUI(v) //format here
});
var chartObj = {
header: 'New Backlog',
description: 'Total Value of Recently Added (Last 30 Days) Backlog by Issue Type',
type: 'chart',
id: 'div4',
layout: {
margin: {
autoexpand: true,
r: 25,
t: 20,
b: 20,
l: 25
},
legend: {
'orientation': 'h',
xanchor: 'center',
yanchor: 'top',
y: -0.1, // play with it
x: 0.5 // play with it
},
},
data: [{
values: [15588, 16787, 27778],
labels: ['Bug', 'Improvement', 'Story'],
text: text,
type: 'pie',
textinfo: 'label+text',
hoverinfo: 'label+text+percent'
}],
};
var myPlot = document.getElementById('div4');
Plotly.plot(myPlot, chartObj);
function currencyFormatterForUI(value) {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0
});
return formatter.format(value || 0);
}
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="div4"></div>

Resources