How to calculate needle position in plotly gauge chart - r

I'm trying to create a gauge chart using plotly in R/Shiny and am having a hard time figuring out how to determine the (x,y) coordinates of the point of the needle depending on value.
My gauge min = 18792 and max = 29472, with a mid-point of 24132. I use the following code to calculate (x,y) coords:
x1 = round(0.5 + (0.2 * cos(deg2rad(180-degs))), digits = 3)
y1 = round(0.5 + (0.2* sin(deg2rad(180-degs))), digits = 3)
I'm assuming the circle origin is (0.5,0.5). I need the needle to be ~ 0.2 in length (so r = 0.2).
As expected, whenever the minimum value is reached (18792), the needle position is (0.3, 0.5), and, reviewing it on screen, looks normal:
However, when the value reaches mid-point (24132), while (x,y) come up as expected (0.5, 0.7), the needle doesn't seem to be as long as the previous example:
The code I am using is as follows. You can switch between min and mid by commenting out the appropriate county value:
max = 29472
min = 18792
california = 23364
county = 24132
# county = 18792
min_label = paste0("$", format(min, big.mark = ","))
max_label = paste0("$", format(max, big.mark = ","))
california_label = paste0("$", format(california, big.mark = ","))
county_label = paste0("$", format(county, big.mark = ","))
# Define needle points
perDeg = (max - min)/180
degs = round((county - min)/perDeg, digits = 0)
deg2rad <- function(deg) { deg * pi / 180 }
# Define [x2,y2] - the needle point
x1 = round(0.5 + (0.2 * cos(deg2rad(180-degs))), digits = 3)
y1 = round(0.5 + (0.2* sin(deg2rad(180-degs))), digits = 3)
basePlot <- plot_ly(
type = "pie",
values = c(40, 10, 40, 10),
labels = c(" ", min_label, " ", max_label),
rotation = 108,
direction = "clockwise",
hole = 0.7,
textinfo = "label",
textposition = "outside",
hoverinfo = "none",
domain = list(x = c(0, 1), y = c(0, 1)),
# marker = list(colors = c('rgb(100, 100, 255)', 'rgb(100, 100, 255)', 'rgb(100, 100, 255)', 'rgb(100, 100, 255)')),
showlegend = FALSE,
sort = FALSE
)
basePlot <- add_trace(
basePlot,
type = "pie",
values = c(50, 50),
labels = c("Estimated Annual Cost of Living", " "),
rotation = 90,
direction = "clockwise",
hole = 0.7,
textinfo = "label",
textposition = "inside",
hoverinfo = "none",
domain = list(x = c(0, 1), y = c(0, 1)),
# marker = list(colors = c('rgb(255, 255, 255)', 'rgb(255, 255, 255)')),
showlegend= FALSE
)
basePlot <- layout(
basePlot,
shapes = list(
list(
type = 'path',
path = paste0('M 0.5 0.5 L ', x1, ' ', y1, ' Z'),
xref = 'paper',
yref = 'paper',
fillcolor = 'rgb(226,210,172)'
)
),
annotations = list(
list(
xref = 'paper',
yref = 'paper',
x = 0.5,
y = 0.4,
showarrow = FALSE,
text = paste0('<b>', county_label, '</b>')
)
)
)
basePlot
There has to be something that I'm totally missing. Any help is greatly appreciated!

I know this is a little after the fact, but I've just been experiencing the exact same issue, but with the Python API. I didn't need my guages to be absolutely precise and 'near enough' would do. I resolved the issue by doubling the radius value in the y-axis.
This produced acceptable results and I assume it's because plotly treats both x and y axis as a value between 0 and 1.0. I'm using a rectangular display panel which means x=0.1 and y=0.1 are different measures. Hope this helps somebody else!

**For Python - I had this same issue but using solely Python
In my case, it was because the semi-circle was not actually a semi-circle. If you plot the grid behind your chart using
fig.update_layout(xaxis={'showgrid': True, 'showticklabels':True})
you may find the radius of your semi-circle is not the same along the x & y axis. To fix this, I found another parameter and added another line:
fig.update_layout(autosize=False)
Plot the grid again to check, but for me this made my gauge the same radius along both the x & y axis. Then, my arrow/indicator was the same length all the way around and pointed accurately. Another potential trick, if you want to have your arrow not extend all the way to the outer edge, you can play around with this param:
standoff=30 #try numbers to see what works
Put this param within the arrow annotation and play around with a few values
#TWCars: you can manually add text anywhere on the diagram with a line like this:
fig.add_annotation(x=-.5, y=.5, text="<b>Text</b>", showarrow=False)
where x and y values are the position on the grid the text will display (b tags are to bold, can remove). This is somewhat hacky but could be a fine solution if your figure is static and need the text in one spot at all times.

I would like to put the labels of my doughnut charts in the middle of those, but I tried a solution and it doesn't appear where I want. Here is our code:
p1 <- plot_ly(tab2, labels = ~c("Domicile-Travail","Domicile-ecole","Courses-achats","Utilisation professionnelle","Promenade-loisirs","Autre"), values = ~prop, type = 'pie',textposition = 'middle right',
textinfo = 'percent',subtitle="B", hole= "0.5",domain = list(x = c(0, 0.45), y = c(0, 1)),insidetextfont = list(color = '#FFFFFF'),
hoverinfo = 'text')
p2 <- plot_ly(tab3, labels= ~c("Domicile-Travail","Domicile-ecole","Courses-achats","Utilisation professionnelle","Promenade-loisirs","Autre"), values = ~prop, type = 'pie', textposition = 'middle left',
textinfo = 'percent',hole= "0.5", domain = list(x = c(0.55,1), y = c(0, 1)), insidetextfont = list(color = '#FFFFFF'),
hoverinfo = 'text', annotations=list(text="B",x=0,y=0,label=2013))
subplot(p1,p2)%>%
layout(title = "Nombre d'accidents par type de trajet ",
xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))

Related

Changing the color of a specific ring in a radar chart created in plotly

I have a radar chart created w/ plotly as shown below. Is there a way to have only the ring at 100 colored red, while all other rings remain in their off-gray color?
library(plotly)
fig <- plot_ly(
type = 'scatterpolar',
r = c(135, 75, 110),
theta = c('A','B','C'),
fill = 'toself'
)
fig %>%
layout(
polar = list(
radialaxis = list(
visible = T,
range = c(0, 150)
)
)
)
To my knowledge, there is not an option to use custom formatting on any axis line other than zero, so there may not actually be a "canonical" answer here that you're hoping for.
That being said, adding another trace to your plot might be the best work-around, even if it's a little clunky. Because the added line trace overrides the default categorical axis, some more extensive customization of the angular axis and some math to manually calculate categorical values relative to 360 degree coordinate system (this is normally obscured from the user) is required.
Names <- c('A','B','C')
Values <- c(135, 75, 110)
ThetaValues <- seq(from = 0, to = 360, length.out = length(Values)+1L)[seq_len(length(Values - 1L))]
plot_ly() %>%
add_trace(type = 'scatterpolar',mode = "lines",
r = rep(100,100),
theta = seq(from = 0, to = 360, length.out = 100),
line = list(smoothing = 1,
color = "red",
shape = "spline"),
hoverinfo = "skip",
showlegend = FALSE) %>%
add_trace(type = 'scatterpolar',mode = "markers",
r = Values,
name = "",
theta = ThetaValues,
color = I("blue"),
fill = 'toself') %>%
layout(polar = list(radialaxis = list(visible = T,
range = c(0, 150)),
angularaxis = list(visible = T,
type = "category",
ticktext = Names,
tickvals = ThetaValues)))

Plot multiple legends in R-plotly

I have 5 continuous variables that I'd like to graph together in R plotly.
I wrote the following code and got the plot to run as expected, but I cannot figure out how to deal with the legends. As is, the color legend appears, but the size legend does not.
I would like to plot both legends and control their locations within the plot. Suggestions from a similar post Adding color and bubble size legend in R plotly do not solve the problem.
Here's the code and sample data:
x<-sample(30)
y<-sample(30)
z<-sample(30)
c<-sample(30)
s<-sample(30)
fig <- plot_ly (x = x, y = y, z = z, color = c,
colors = c("#440154FF", "#1F968BFF", "#FDE725FF"), size = s,
marker = list(symbol = 'circle', sizemode = 'diameter'), sizes = c(1, 30))
fig <- fig %>% add_markers()
fig <- fig %>% layout(scene = list(xaxis = list (title = 'X'),
yaxis = list(title = 'Y'),
zaxis = list(title = 'Z'),
annotations = list(x = 1.05, y =1.02,
text = 'Gradient title',
xref = 'paper', yref = 'paper',
showarrow=FALSE, showlegend=TRUE)))
fig
It's been a while since this question was asked, but I have an answer. Initially, I tried to make the legend a subplot, but the legend from the 3D markers is offset from the plot-as-a-legend of bubble sizes. To fix that issue, I created an image of the bubbles and added it to the original plot as an image.
Using the information from fig in your original code, I created another figure (the bubbles and sizes).
figB <- plot_ly(x = 1, y = seq(30, 5, by = -5),
size = seq(30, 5, by = -5),
sizes = c(1, 30),
type = "scatter",
mode = "markers",
color = seq(30, 5, by = -5),
colors = c("#440154FF", "#1F968BFF", "#FDE725FF"),
marker = list(sizeref = 0.1,
sizemode = "area"),
height = 275, width = 100) %>%
layout(
xaxis = list(zeroline = F, showline = F, showticklabels = F, showgrid = F),
yaxis = list(showgrid = F, side = "right")) %>% # numbers on right (as fig legend)
hide_colorbar()
figB
I used three different libraries for this next part: htmlwidgets, webshot, and magick.
# create temp files
tmp <- tempfile(fileext = ".html") # plotly to html
tmp2 <- tempfile(fileext = ".png") # html to png
# create html
htmlwidgets::saveWidget(figB, tmp, background = "transparent")
# create png
webshot::webshot(tmp, tmp2, zoom = 2, vwidth = 150, vheight = 275) # to get great res
# make the png an object
itsBack <- magick::image_read(tmp2)
# check the amount of white space
magick::image_border(itsBack, "gray") # not too much white space; good res
unlink(tmp) # remove tempfile connection
unlink(tmp2)
For this last step, I copied the code from your original figure. The image needs to be added to layout. I removed code that didn't impact the figure, as well.
# set up placement of image below the initial legend
imgr = list(
source = raster2uri(as.raster(itsBack)),
xref = "paper",
yref = "paper",
y = .5, # paper domain is 0 to 1, this puts the top in the middle
x = .95, # almost all the way right
sizex = .45, # scale image down (0-1)
sizey = .45, # scale image down (0-1)
opacity = 1,
layer = "above")
# Rebuild fig without the initial legend - then add imgr to the legend
fig <- plot_ly (x = x, y = y, z = z, color = c,
colors = c("#440154FF", "#1F968BFF", "#FDE725FF"),
size = s,
marker = list(symbol = 'circle',
sizemode = 'diameter'),
sizes = c(1, 30))
fig <- fig %>% layout(
scene = list(xaxis = list(title = 'X'),
yaxis = list(title = 'Y'),
zaxis = list(title = 'Z')),
images = imgr) # adding bubbles here
fig
Depending on what you're doing with the graph, the placement and scaling may need to be adjusted. While plotly objects scale dynamically, the png won't be nearly as dynamic-friendly. The image is scaled down to 45% of its original size, so you have a lot of room to grow, but you may have to adjust those parameters (sizex and sizey). If you rescale your viewer window, you may also need to refresh the view. (Use the refresh icon in the Viewer pane.)

Animated plotly gauge plot with changing colors and text annotation

I have a gauge plot I want to animate (following this tutorial), with the color of the plot changing according to the current value. I also want an annotation* which displays the current value of the gauge (I don't want the standard value displayed in the sector).
For the record, I'm trying to use plotly instead of c3::c3_gauge because I hope to eventually embed this in a plotly::subplot() with other plots that'll animate simultaneously.
I currently have the following:
library(plotly)
library(RColorBrewer)
riskToHex <- function(x) {
x <- colorRamp(rev(brewer.pal(11, "RdYlBu")))(x / 100)
rgb <- paste(x[,1], x[,2], x[,3], sep = ",")
paste0("rgb(", rgb, ")")
}
dd <- data.frame(values = c(90, 60, 20))
dd <- dd %>%
mutate(colors = riskToHex(dd$values),
frame = seq.int(nrow(dd)))
dd <- merge(dd, data.frame(values = 200 - dd$values,
colors = "white",
frame = dd$frame),
all = TRUE) %>%
arrange(frame)
plot_ly() %>%
add_pie(values = dd$values,
frame = dd$frame,
rotation = -90,
marker = list(colors = dd$colors),
textinfo = "none",
title = list(text = "Risk score",
font = list(size = 20)),
hoverinfo = "skip",
sort = FALSE,
showlegend = FALSE,
order = "clockwise",
hole = 0.6) %>%
add_markers(x = 0,
y = c(0, 1),
color = 'rgba(0,0,0,1)') %>%
add_text(x = 0,
y = 0.6,
yshift = 100,
text = filter(dd, colors != "white")$values,
frame = distinct(dd, frame)[[1]],
showlegend = FALSE,
textfont = list(size = 20)) %>%
animation_opts(frame = 500) %>%
layout(
xaxis = list(showgrid = FALSE,
zeroline = FALSE,
showticklabels = FALSE
),
yaxis = list(showgrid = FALSE,
zeroline = FALSE,
showticklabels = FALSE
)
)
This is rather messy and very hacky. I had to use add_text() because I couldn't figure out how to get annotations to work in this case with changing values in each frame (whether through add_annotations or layout(annotations = ...)). Unfortunately, the add_text() forced me to add the layout block disabling the axes (which otherwise start to appear). And since the pie chart is seemingly printed on paper space, I also had to add two invisible points so that I could actually position the text (otherwise the axes would always keep the text centered).
Regardless, this successfully generates an animated chart. However, the color only seems to change to the correct value on the following frame (by which time it's too late). I don't believe it's just a processing delay or whatever because accelerating the animation speeds up the color changes as well.
Interestingly, this only happens due to the add_text() block. If I comment that out, the colors on the bar work correctly.
Is there any explanation and solution to this?
* I'm open to other solutions!

Add a line that indicates the average value of an axis in a plotly scatterplot [duplicate]

I'm using the plotly package and I'm trying to add a horizontal line to a graph. Is there any way of doing it using plotly?
It can be done using ggplot2 and the ggplotly function as shown below:
library(plotly)
p <- ggplot() +
geom_hline(yintercept = 4) +
xlim(c(0,10)) +
ylim(c(0,10))
ggplotly(p)
But I can't add this to an existing plotly plot.
Also, the axis of my charts are not fixed, so it would be difficult (but not impossible) to just work out an x and y coordinate system for a horizontal line, but I'd rather just add one automatically.
I've looked into the y0 and dy arguments, but I can't seem to get the code for those to work, either. I'm not quite sure what they do exactly, but I think they're maybe what I'm looking for? I can't find good examples of their usage.
There are two main ways to do this (using either data or 'paper' coordinates). Assuming data coordinates, the easiest current way is via add_segments():
plot_ly() %>%
add_segments(x = 4, xend = 4, y = 0, yend = 10) %>%
add_segments(x = 3, xend = 5, y = 5, yend = 5)
Notice how we've hard coded the extent of these lines in data coordinates; so when zooming and panning the plot, the line will be "clipped" at those values. If you don't want these lines to be clipped, use a line shape with xref/yref set to paper (this puts the graph region on a 0-1 scale, rather than on the x/y data scale):
vline <- function(x = 0, color = "red") {
list(
type = "line",
y0 = 0,
y1 = 1,
yref = "paper",
x0 = x,
x1 = x,
line = list(color = color)
)
}
hline <- function(y = 0, color = "blue") {
list(
type = "line",
x0 = 0,
x1 = 1,
xref = "paper",
y0 = y,
y1 = y,
line = list(color = color)
)
}
plot_ly() %>%
layout(shapes = list(vline(4), hline(5)))
Alternatively, you could add a shape (i.e. line) under layout(). The following example adds a vertical line:
p <- plot_ly(data, x = ~x.data, y = ~y.data, text = ~text.data, type = 'scatter',
mode = 'markers', marker = list(size = ~size.data, opacity= 0.5)) %>%
layout(shapes=list(type='line', x0= 0.2, x1= 0.2, y0=min(allyvalues), y1=max(allyvalues), line=list(dash='dot', width=1)),
title = 'This is the Title',
xaxis = list(title = "X-Axis", showgrid = TRUE),
yaxis = list(title = "Y-Axis", showgrid = TRUE))
p
Building on Carson's nice answer above, here is a convenience function closer to ggplot's geom_vline()
# Add vertical line(s) at position x to plotly plot p
# Additional arguments: color, width (px), dash ('solid','dot', 'dash', etc)
# See https://plotly.com/r/reference/#layout-shapes-items-shape-line
add_vline = function(p, x, ...) {
l_shape = list(
type = "line",
y0 = 0, y1 = 1, yref = "paper", # i.e. y as a proportion of visible region
x0 = x, x1 = x,
line = list(...)
)
p %>% layout(shapes=list(l_shape))
}
To make the function additive the following modifications to the function can be used
add_vline = function(p, x, ...) {
if(!is.null(p$x$layoutAttrs)){
index <- unname(which(sapply(p$x$layoutAttrs, function(x)
!is.null(x$shapes))))
} else {
index <- integer()
}
l_shape = list(
type = "line",
y0 = 0, y1 = 1, yref = "paper", # i.e. y as a proportion of visible region
x0 = x, x1 = x,
line = list(
...
),
layer = "below"
)
if(length(index) > 0){
shapes <- p$x$layoutAttrs[[index]]$shapes
shapes[[length(shapes) + 1]] <- l_shape
p$x$layoutAttrs[[index]]$shapes <- shapes
} else {
p <- plotly::layout(
p = p,
shapes = list(l_shape)
)
}
p
}

axis from plotly chart in R

I am trying to reproduce this Gantt chart with the plotly in R ( the chart I want). I have a dataframe with 6 columns and I want to have text on the y axis and months with years on x axis. Based on my dataframe I have the following :
one=c('bla','bla','bla',
'bla','bla','bla','bla','bla','bla','bla',
'bla','bla')
two=c('09/25/2017','10/02/2017','11/15/2017','11/29/2017','01/01/2018','01/01/2018','04/01/2018','07/01/2018','09/01/2018','09/01/2018',
'08/01/2020','09/01/2020')
three=c(1102,55,46,214,181,181,122,62,700,700,31,30)
four=c('bla','bla','bla',
'bla','bla','bla','bla',
'bla','bla','bla'
,'bla','bla')
five=c('A','B','C','D','E','F','G','H','E','I','J','E')
df=data.frame(one,two,three,four,five)
df$two =as.Date(df$two,"%m/%d/%Y")
client = "my example"
# Choose colors based on number of resources
cols <- RColorBrewer::brewer.pal(length(unique(df$five)), name = "Set3")
df$color <- factor(df$five, labels = cols)
# Initialize empty plot
p <- plot_ly()
# Each task is a separate trace
# Each trace is essentially a thick line plot
# x-axis ticks are dates and handled automatically
for(i in 1:(nrow(df))){
p <- add_trace(p,
x = c(df$two[i], df$two[i] + df$three[i]), # x0, x1
y = c(i, i), # y0, y1
mode = "lines",
line = list(color = df$color[i], width = 20),
showlegend = F,
hoverinfo = "text",
# Create custom hover text
text = paste("Task: ", df$one[i], "<br>",
"Duration: ", df$three[i], "days<br>",
"Resource: ", df$five[i]),
evaluate = T # needed to avoid lazy loading
)
}
# Add information to plot and make the chart more presentable
p <- layout(p,
# Axis options:
# 1. Remove gridlines
# 2. Customize y-axis tick labels and show task names instead of numbers
xaxis = list(showgrid = F, tickfont = list(color = "#e6e6e6")),
yaxis = list(showgrid = F, tickfont = list(color = "#e6e6e6"),
tickmode = "array", tickvals = 1:nrow(df), ticktext = unique(df$one),
domain = c(0, 0.9)),
# Annotations
annotations = list(
# Add total duration and total resources used
# x and y coordinates are based on a domain of [0,1] and not
# actual x-axis and y-axis values
list(xref = "paper", yref = "paper",
x = 0.80, y = 0.1,
text = paste0("Total Duration: ", sum(df$three), " days<br>",
"Total Resources: ", length(unique(df$five)), "<br>"),
font = list(color = "#ffff66", size = 12),
ax = 0, ay = 0,
align = "left"),
# Add client name and title on top
list(xref = "paper", yref = "paper",
x = 0.1, y = 1, xanchor = "left",
text = paste0("Gantt Chart: ", client),
font = list(color = "#f2f2f2", size = 20, family = "Times New Roman"),
ax = 0, ay = 0,
align = "left")
),
plot_bgcolor = "#333333", # Chart area color
paper_bgcolor = "#333333") # Axis area color
p
the first column (one) is a text
So my questions are:
How can I get the text from tasks (column one) on my y axis (instead of numbers)?
How can I get all the months on x axis?
Thank you.
Answer for question 1:
The reason your current code doesn't do what you would like is because of this:
ticktext = unique(df$one)
Since df$one contains 12 identical values, there is only 1 unique value, and hence not 12 as you would need. To fix this, you can either just use ticktext = df$one or make sure that your labels in df$one are unique (as is the case in the example you linked to). For example, changing df$one to bla1,bla2, ..., bla12 would work for your current example.
And question 2:
To specify the tick interval on your x-axis, you can use the dtick argument. In your case, this would result in the following addition to your line of code for the x-axis:
xaxis = list(showgrid = F, tickfont = list(color = "#e6e6e6"),
dtick = "M1")
where the M is to specify that you want intervals in months, and the 1 specifies that you want the interval to be 1 month (shocking!). FYI, this will automatically change the direction of the tick labels to vertical, if you would like to adapt this, you can use the tickangle argument.

Resources