axis from plotly chart in R - 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.

Related

R Markdown not able to display two plots within the same if-statement

I am creating an R-Markdown document to help with reporting final exam results at our school. For the mathematics exam, I need a conditional statement to display appropriate plots, because the students do not need to take an oral exam (Oral = NA) if their written score is above a certain threshold. So I have an if-statement that checks whether the sum of the Oral_Exam variable (1 for those who had to take it, 0 otherwise) is larger than zero, and if so, create a 3D scatterplot where the students who had to take an oral exam are marked with red, followed by another plot of the same type, only with the students who had to go to the oral exam, colored according to oral exam result. If none of the students had to go to an oral exam, it is checked in a later if-statement, and only one plot is produced. My code looks like this:
```{r warning = FALSE, message = FALSE, echo = FALSE, eval = params$subj == "Matematika"}
if(sum(fulldata$Oral_exam) > 0){
fulldata_color = fulldata %>% mutate(Oral_exam, = as.character(Oral_exam), color = recode(Oral_exam, '1' = "red", '0' = "green"))
div(plot_ly(data = fulldata_color, x = ~Long_A_percent, y = ~Long_B_percent, z = ~Short_percent, marker = list(color = ~color), type="scatter3d", mode="markers", text = ~Name, width = 800, height = 800) %>% layout(
scene = list(aspectmode = "cube", xaxis = list(range = c(0,100), title = 'Long A (x)'),yaxis = list(range = c(0,100), title = 'Long B (y)'), zaxis = list(range = c(0,100), title = 'Short (z)'))), align = "center")
enter code here
Oral_data = fulldata %>% filter(!is.na(Oral_percent))
div(plot_ly(data = Oral_data, x = ~Long_A_percent, y = ~Long_B_percent, z = ~Short_percent,color = ~Oral_percent, type="scatter3d", mode="markers", text = ~Name, width = 800, height = 800) %>% layout(
scene = list(aspectmode = "cube", xaxis = list(range = c(0,100), title = 'Long A (x)'),yaxis = list(range = c(0,100), title = 'Long B (y)'), zaxis = list(range = c(0,100), title = 'Short (z)'))), align = "center")
}
This code, when knit, results in only the second plot being created, and it looks like the way I intend it to. However, if I break it up into two if statements with the same condition, and put one plotting command (and the corresponding command for the creation of the data frame), both plots are displayed correctly
I can work around it by having two if-statements instead of two, but it would be good to know why it doesn't work, especially since I have used multiple plots in the same code chunk (although not in the same if-statement) in the same document, and it has always worked as intended.
You can store plotly objets in variables and print them outside if:
```{r}
p1 <- NULL
p2 <- NULL
if(TRUE) {
p1 <- plot_ly(x = 1, y = 1, type = "scatter", mode = "marker")
p2 <- plot_ly(x = 1, y = 10, type = "scatter", mode = "marker")
}
p1
p2
```

R: How to ignore shapes when autoscaling chart's axis in plotly?

I have a plotly chart where the x-axis is date. I have added a rectangle shape to highlight a specified date period. Sometimes this date period will lie within the date range of the data displayed in the chart, in which case there is no problem. The chart appears as normal. However, it is possible that the date period lies outside of the date range in the chart (or it overlaps, with part of it outside the date range of the chart). In this case, when the chart is produced, the x-axis is autoscaled to show the full rectangle as well as the data (or the traces).
This is not what I want. I want it to be autoscaled on the date range of the data (traces) only (as if the rectangle didn't exist), but with the rectangle added in the background of the chart. If the user manually zooms out they should be able to see the full rectangle, including the part that lies outside of the data, but this should not be the default.
See the below code as an example. It is an edited version of the code taken from here: https://plotly.com/r/shapes/#lines
The date range of the data is 1967-2015. The autoscale of the x-axis should be based on this only. However, as one of the rectangles has x coordinates of 1940 and 1945, the x-axis is autoscaled to 1940-2015 in order to display the full rectangle, leaving a large gap between 1940 and 1967 with no data. I want it to be so that if the user manually zooms out to a scale of 1940-2015, this is what they would see, but the autoscale should ignore the rectangle and default to a range of 1967-2015.
library(plotly)
fig <- plot_ly(economics, x = ~date, y = ~uempmed, name = "unemployment")
# add shapes to the layout
fig <- layout(fig, title = 'Highlighting with Rectangles',
shapes = list(
list(type = "rect",
fillcolor = "blue", line = list(color = "blue"), opacity = 0.3,
x0 = "1940-01-01", x1 = "1945-01-01", xref = "x",
y0 = 0, y1 = 1, yref = "paper"),
list(type = "rect",
fillcolor = "blue", line = list(color = "blue"), opacity = 0.2,
x0 = "2000-01-01", x1 = "2005-01-01", xref = "x",
y0 = 0, y1 = 1, yref = "paper")))
fig
There does not appear to be anything in the documentation that addresses this issue, and I can't find anything on Google either. But I'd be surprised if I'm the only person who's ever wanted to do this, so I think there must surely be an option.
Does anyone know?
EDIT: I have discovered that I can define the range of the x-axis manually, and can take this to be the minimum and maximum of the x column. However, two drawbacks of this approach. One is that the outer points lie exactly on the boundary, so half of the point or bar (if you have a bar chart) gets cropped out which doesn't look good.
The other potential drawback is when you click the autoscale button it still zooms right out to see the rectangles outside of the range. This may or may not be desirable, but it will be good to know if an alternative method exists.
library(plotly)
fig <- plot_ly(economics, x = ~date, y = ~uempmed, name = "unemployment")
minx <- min(economics$date)
maxx <- max(economics$date)
x_axis <- list(
range = c(minx,maxx)
)
# add shapes to the layout
fig <- layout(fig, xaxis= x_axis, title = 'Highlighting with Rectangles',
shapes = list(
list(type = "rect",
fillcolor = "blue", line = list(color = "blue"), opacity = 0.3,
x0 = "1940-01-01", x1 = "1945-01-01", xref = "x",
y0 = 0, y1 = 1, yref = "paper"),
list(type = "rect",
fillcolor = "blue", line = list(color = "blue"), opacity = 0.2,
x0 = "2000-01-01", x1 = "2005-01-01", xref = "x",
y0 = 0, y1 = 1, yref = "paper")),
xaxis = list(range=c(as.Date("1980-01-01"),as.Date("2000-01-01")))
)
fig

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.)

Plotly Scatter Plot: Symbol disrupts the colorscale and categorical x-y positions

I'm creating a unique type of categorical scatter plot with plotly in R, where:
color is indicated by a continuous numeric variable
size is indicated by a continuous numeric
symbol is indicated by a categorical
both axis are categorical variables
It appears that calling "symbol" and "symbols" is affecting the color scale numbers as well as the proper location of markers.
Here is my demo code which includes hoverinfo so you can see that the "NA" for Top/C is not in the correct position.
library(plotly)
m <- data.frame(c('A','A','B','B','C','C'),
c('Top','Bottom','Top','Bottom','Top','Bottom'),
c('OK','NG','OK','OK','OK','NG'),
c(8,20,55,72,NA,43), # Top,C should be blank
c(4,5,6,7,8,9))
colnames(m) <- c("cat_1","cat_2","check","Bigness", "Twistiness")
color_scale <- list(c(0,'rgb(227,120,0)'),c(0.5,'rgb(220,220,220)') , c(1,'rgb(43,92,138)'))
p <- plot_ly(data = m,
x = ~cat_1,
y = ~cat_2,
type='scatter',
mode='markers',
symbol = ~check, ## problem line
symbols = c('square-open','square'), ## problem line
marker = list(color = ~Twistiness,
size= ~Bigness,
colorscale = color_scale,
colorbar = list(len = 0.8, y = 0.3)),
text = ~paste0('cat_1 (x): ', cat_1, '\n',
'cat_2 (y): ', cat_2, '\n',
'Big (size): ', Bigness, '\n',
'Twist (color): ', Twistiness, '\n')
) %>%
layout(margin = list(r = 8, t = 15, b = 50, l = 80),
# xaxis = list(title = ""),
yaxis = list(title = ""),
showlegend = TRUE)
p
Picture of executed code
It seems that plotly is the best way to add interactive hover as well as pass event_data to another code block in Shiny.
I have tried this via ggplotly, but the event_data is not correct and the legend cannot have custom positioning.

How to calculate needle position in plotly gauge chart

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))

Resources