I have a dataframe that looks like:
## Data
df <- data.frame(label = c("A", "B", "C", "D"),
color = c("red", "red", "blue", "green"),
y = c(10, 11, 12, 13))
"A" and "B" are part of the same category, while "C" and "D" are part of separate categories.
I would like to add a legend on the chart with category labels.
## Highchart without Legend
## Basic highchart
h <- rCharts:::Highcharts$new()
h$chart(type = "column")
## Highchart data:
h$series(showInLegend = FALSE, data = rCharts::toJSONArray2(df[, c("label", "color", "y")], json = FALSE, names = TRUE))
## Highchart options:
h$xAxis(categories = unique(df$label), labels = list(rotation = 0, align = 'center', style = list(fontSize = '12px', fontFamily = 'Verdana, sans-serif')), replace = FALSE)
h$tooltip(formatter = "#! function() {return this.x + ': ' + this.y; } !#")
h$plotOptions(series = list(color = df$color), column = list(grouping = FALSE))
h # display highchart
I haven't found a method that makes any sense to solve this problem.
Any help would be appreciated.
Disclaimer: I know the question says rCharts, I just want to add an alternative using highcharter package.
Like #Optimus said, the issue is add multiples series. In case you have arbitraty number of series (colors in your example) and want add it automatically you can use highcharter with allow you to add multiples series from a list of data series with the hc_add_series_list function.
library(highcharter)
library(dplyr)
df <- data_frame(label = c("A", "B", "C", "D"),
color = c("red", "red", "blue", "green"),
y = c(10, 11, 12, 13),
x = c(1:4)-1)
head(df)
series <- df %>%
group_by(color) %>% # each serie is from one color
do(data = list.parse3(.)) %>%
ungroup() %>%
mutate(name = paste("I'm color", color)) %>%
list.parse3()
Here list.parse3 is similar to toJSONArray2.
series[[1]]
$color
[1] "blue"
$data
$data[[1]]
$data[[1]]$label
[1] "C"
$data[[1]]$color
[1] "blue"
$data[[1]]$y
[1] 12
$data[[1]]$x
[1] 2
$name
[1] "I'm color blue"
Finally:
highchart() %>%
hc_chart(type = "column") %>%
hc_add_series_list(series) %>%
hc_xAxis(categories = df$label) %>%
hc_plotOptions(column = list(grouping = FALSE))
The result will be:
And here's how I solved it.
Each 'category' is split into a separate series with an x value to determine its location on the graph (highcharts requires this for some reason. Without it, the graphs stack).
Here's a sample code that works:
## Highchart with Legend
## Remark: simply switching showInLegend to TRUE will not work
df$x <- c(0, 1, 2, 3) # add an index to dataframe (starting from 0)
# and pass it to h$series data
h <- rCharts:::Highcharts$new()
h$chart(type = "column")
h$series(name = "Category 1 (Red)", color = "red", data = rCharts::toJSONArray2(df[df$color == "red", c("label", "color", "x", "y")], json = FALSE, names = TRUE))
h$series(name = "Category 2 (Blue)", color = "blue", data = rCharts::toJSONArray2(df[df$color == "blue", c("label", "color", "x", "y")], json = FALSE, names = TRUE))
h$series(name = "Category 3 (Green)", color = "green", data = rCharts::toJSONArray2(df[df$color == "green", c("label", "color", "x", "y")], json = FALSE, names = TRUE))
h$xAxis(categories = unique(df$label), labels = list(rotation = 0, align = 'center', style = list(fontSize = '12px', fontFamily = 'Verdana, sans-serif')), replace = FALSE)
h$tooltip(formatter = "#! function() {return this.x + ': ' + this.y; } !#")
h$plotOptions(series = list(color = df$color), column = list(grouping = FALSE))
h # display chart
I hope this helps someone else.
Related
I have a data frame with 2 columns, one that I want to use as a toggle (so display grp1 or grp2) and another where I want to split the data into different lines. I can't seem to figure out how to get it to work properly with plotly, I think there should be a simple straightforward way to do it but for the life of me I can't get it to stop mixing up the groups.
library(tidyverse)
library(plotly)
library(ggplot2)
# example data my group 1 would be social, group 2 would be grp
df1 = data.frame(grp = "A", social = "Facebook",
days = c("2020-01-01","2020-01-02","2020-01-03","2020-01-04"),
yval = c(0.1, 0.2, 0.3, 0.4))
df2 = df1
df2$grp = "B"
df2$yval = df2$yval + 0.2
df3 = df1
df3$grp = "C"
df3$yval = df3$yval + 0.4
df = rbind(df1, df2, df3)
aux = df
aux$social = "Twitter"
aux$yval = aux$yval + 0.1
df = rbind(aux, df)
rm(aux, df1, df2, df3)
df$days = as.Date(df$days)
df$social_group = paste(df$social, df$grp)
ggplot(data = df, mapping = aes(x = days, y = yval, color = social)) + geom_point() + geom_line() + facet_wrap(facets = ~social)
So what I'm trying to do is to create a plotly that lets me switch between the ggplot facets, by toggling a Facebook or Twitter button.
This is what I currently got, which starts well, but as soon as I toggle the buttons the groups seem to mix, which shouldn't be happening when I consider I'm filtering on another column...
facebook_annotations <- list(
data=df %>% filter(social=="Facebook"),
x=~days,
y=~yval,
color = ~grp,
hovertemplate = paste('%{x}', '<br>Hover text: %{text}<br>'),
text=~days
)
twitter_annotations <- list(
data=df %>% filter(social=="Twitter"),
x=~days,
y=~yval,
color = ~grp,
hovertemplate = paste('%{x}', '<br>Hover text: %{text}<br>'),
text=~days
)
# updatemenus component
updatemenus <- list(
list(
active = 0,
type = "buttons",
buttons = list(
list(
label = "Facebook",
method = "update",
args = list(list(visible = c(TRUE, FALSE)),
list(title = "Facebook",
annotations = list(facebook_annotations, c())))),
list(
label = "Twitter",
method = "update",
args = list(list(visible = c(FALSE, TRUE)),
list(title = "Twitter",
annotations = list(c(), twitter_annotations)))))
)
)
fig <- df %>% plot_ly(type="scatter", mode="lines")
fig <- fig %>% add_lines(
data=df %>% filter(social=="Facebook"),
x=~days,
y=~yval,
color = ~grp,
hovertemplate = paste('%{x}', '<br>Hover text: %{text}<br>'),
text=~days
)
fig <- fig %>% add_lines(
data=df %>% filter(social=="Twitter"),
x=~days,
y=~yval,
color = ~grp,
hovertemplate = paste('%{x}', '<br>Hover text: %{text}<br>'),
text=~days,
visible=FALSE
)
fig <- fig %>% layout(title="Facebook",
xaxis=list(title=""),
yaxis = list(range = c(0, 1), title = "My Title"),
updatemenus=updatemenus)
fig
What am I missing? It's driving me crazy, I'm even considering just adding each group as an individual trace, but that's not really practical when my actual case study has 8 groups...
I used mschart to generate a chart in R. However:
(1) I can't find how to change the legend text. Now the legend text is "a" and "b". I hope to change to other string, such as "legend text 1" and "legend text 2".
(2) If I change "a" and "b" to chinese characters, such "中国" and "北京" I got an error. The new word file can't be opened. If I changed back to English characters, it works again!
library(officer)
library(magrittr)
library(mschart)
doc <- read_docx()
newdata <- data.frame(
Name = c("a","a","a","a","b","b","b","b"),
wave_id = c(
"2017Q1", "2017Q2", "2017Q3", "2017Q4", "2017Q1", "2017Q2", "2017Q3", "2017Q4"
),
pct = c(0.68,0.71,0.70,0.72,0.57,0.57,0.57,0.58)
)
my_barchart <- ms_barchart(data = newdata, x = "wave_id", y = "pct", group = "Name")
my_barchart <- chart_settings(
x = my_barchart, dir="horizontal", grouping = "stacked", overlap = 100
)
my_barchart <- chart_labels(
my_barchart, title = NULL, xlab = NULL, ylab = NULL
)
my_barchart <- chart_data_labels(
my_barchart, position = "ctr", show_val = TRUE,
separator = ",", show_cat_name = FALSE
)
fp_text_settings <- list(a = fp_text(font.size = 7), b = fp_text(font.size = 7))
my_barchart <- chart_labels_text(my_barchart, fp_text_settings)
mytheme <- mschart_theme(
axis_title_x = fp_text(color = "red", font.size = 24, bold = TRUE),
axis_title_y = fp_text(color = "green", font.size = 12, italic = TRUE),
grid_major_line_x = fp_border(width = 0, color = "orange"),
axis_ticks_y = fp_border(width = 1, color = "orange"))
my_barchart <- set_theme(my_barchart, mytheme)
doc %>% body_add_chart(my_barchart)
print(doc, target = "new.docx")
I am trying to generate multiple graphs in Plotly for 30 different sales offices. Each graph would have 3 lines: sales, COGS, and inventory. I would like to keep this on one graph with 30 buttons for the different offices. This is the closest solution I could find on SO:
## Create random data. cols holds the parameter that should be switched
l <- lapply(1:100, function(i) rnorm(100))
df <- as.data.frame(l)
cols <- paste0(letters, 1:100)
colnames(df) <- cols
df[["c"]] <- 1:100
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly(df,
type = "scatter",
mode = "lines",
x = ~c,
y= ~df[[cols[[1]]]],
name = cols[[1]])
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[-1]) {
p <- p %>% add_lines(x = ~c, y = df[[col]], name = col, visible = FALSE)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(cols, function(col) {
list(method="restyle",
args = list("visible", cols == col),
label = col)
})
)
)
)
print(p)
It works but only on graphs with single lines/traces. How can I modify this code to do the same thing but with graphs with 2 or more traces? or is there a better solution? Any help would be appreciated!
### EXAMPLE 2
#create fake time series data
library(plotly)
set.seed(1)
df <- data.frame(replicate(31,sample(200:500,24,rep=TRUE)))
cols <- paste0(letters, 1:31)
colnames(df) <- cols
#create time series
timeseries <- ts(df[[1]], start = c(2018,1), end = c(2019,12), frequency = 12)
fit <- auto.arima(timeseries, d=1, D=1, stepwise =FALSE, approximation = FALSE)
fore <- forecast(fit, h = 12, level = c(80, 95))
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly() %>%
add_lines(x = time(timeseries), y = timeseries,
color = I("black"), name = "observed") %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 2], ymax = fore$upper[, 2],
color = I("gray95"), name = "95% confidence") %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 1], ymax = fore$upper[, 1],
color = I("gray80"), name = "80% confidence") %>%
add_lines(x = time(fore$mean), y = fore$mean, color = I("blue"), name = "prediction")
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[2:31]) {
timeseries <- ts(df[[col]], start = c(2018,1), end = c(2019,12), frequency = 12)
fit <- auto.arima(timeseries, d=1, D=1, stepwise =FALSE, approximation = FALSE)
fore <- forecast(fit, h = 12, level = c(80, 95))
p <- p %>%
add_lines(x = time(timeseries), y = timeseries,
color = I("black"), name = "observed", visible = FALSE) %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 2], ymax = fore$upper[, 2],
color = I("gray95"), name = "95% confidence", visible = FALSE) %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 1], ymax = fore$upper[, 1],
color = I("gray80"), name = "80% confidence", visible = FALSE) %>%
add_lines(x = time(fore$mean), y = fore$mean, color = I("blue"), name = "prediction", visible = FALSE)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(cols, function(col) {
list(method="restyle",
args = list("visible", cols == col),
label = col)
})
)
)
)
p
You were very close!
If for example you want graphs with 3 traces,
You only need to tweak two things:
Set visible the three first traces,
Modify buttons to show traces in groups of three.
My code:
## Create random data. cols holds the parameter that should be switched
library(plotly)
l <- lapply(1:99, function(i) rnorm(100))
df <- as.data.frame(l)
cols <- paste0(letters, 1:99)
colnames(df) <- cols
df[["c"]] <- 1:100
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly(df,
type = "scatter",
mode = "lines",
x = ~c,
y= ~df[[cols[[1]]]],
name = cols[[1]])
p <- p %>% add_lines(x = ~c, y = df[[2]], name = cols[[2]], visible = T)
p <- p %>% add_lines(x = ~c, y = df[[3]], name = cols[[3]], visible = T)
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[4:99]) {
print(col)
p <- p %>% add_lines(x = ~c, y = df[[col]], name = col, visible = F)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(0:32, function(col) {
list(method="restyle",
args = list("visible", cols == c(cols[col*3+1],cols[col*3+2],cols[col*3+3])),
label = paste0(cols[col*3+1], " ",cols[col*3+2], " ",cols[col*3+3] ))
})
)
)
)
print(p)
PD: I only use 99 cols because I want 33 groups of 3 graphs
I am trying to create a shiny app with a plotly output.
The plot should have multiple y axes, and update based on the variables selected.
The question is how to combine the shiny reactivity and plotly while using add_lines, as at the moment if I select less variables than add_lines the code does not function
Sample code:
library(shiny)
library(dplyr)
library(plotly)
library(tidyr)
data <- cbind(
seq(from = 1, to = 30, by = 1),
sample(seq(from = 100, to = 300, by = 10), size = 30, replace = TRUE),
sample(seq(from = 1, to = 100, by = 9), size = 30, replace = TRUE),
sample(seq(from = 50, to = 60, by = 2), size = 30, replace = TRUE),
sample(seq(from = 100, to = 130, by = 1), size = 30, replace = TRUE)
) %>%
as.data.frame()
names(data) <- c("date", "a", "b", "x", "y")
data <- data %>% gather("key", "value", 2:5)
ui <- fluidPage(
column(
width = 3,
selectInput("select", "Select var:", choices = c("a", "b", "x", "y"), selected = c("a", "b", "x"), multiple = TRUE)
),
column(
width = 9
),
column(
width = 12,
plotlyOutput("plot")
)
)
server <- function(input, output){
output$plot <- renderPlotly({
data <- data %>% filter(key %in% c("date", input$select)) %>% spread(key, value)
plot_ly(x = ~data$date) %>%
add_lines(y = ~data[, 2], name = input$select[1], line = list(color = "red")) %>%
add_lines(y = ~data[, 3], name = input$select[2], line = list(color = "blue"), yaxis = "y2") %>%
add_lines(y = ~data[, 4], name = input$select[3], line = list(color = "green"), yaxis = "y3") %>%
layout(
yaxis = list(
side = "left"
),
yaxis2 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.02
),
yaxis3 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.04
)
)
})
}
shinyApp(ui, server)
Here is the solution for you:
library(shiny)
library(dplyr)
library(plotly)
library(tidyr)
data <- cbind(
seq(from = 1, to = 30, by = 1),
sample(seq(from = 100, to = 300, by = 10), size = 30, replace = TRUE),
sample(seq(from = 1, to = 100, by = 9), size = 30, replace = TRUE),
sample(seq(from = 50, to = 60, by = 2), size = 30, replace = TRUE),
sample(seq(from = 100, to = 130, by = 1), size = 30, replace = TRUE)
) %>%
as.data.frame()
names(data) <- c("date", "a", "b", "x", "y")
data <- data %>% gather("key", "value", 2:5)
ui <- fluidPage(
column(
width = 3,
selectInput("select", "Select var:", choices = c("a", "b", "x", "y"), selected = c("a", "b", "x"), multiple = TRUE)
),
column(
width = 9
),
column(
width = 12,
plotlyOutput("plot")
)
)
server <- function(input, output){
output$plot <- renderPlotly({
data <- data %>% filter(key %in% c("date", input$select))
plot_ly(data, x = ~date, y=~value, color=~key) %>%
layout(
yaxis = list(
side = "left"
),
yaxis2 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.02
),
yaxis3 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.04
)
)
})
}
shinyApp(ui, server)
It is very easy, you should not spread your dataset, instead with long format you can setup an argument color= in plotly, which will directly group your data according to set variable:
plot_ly(data, x = ~date, y=~value, color=~key)
At least of ~6 months ago, my understanding (from plotly support) was that plotly did not concisely handle this sort of conditional plotting. Instead, if you can enumerate all plotting scenarios, you can use something like the following (which does not actually work yet, need to fix other parts of your code), with an else if for each plotting scenario:
output$plot <- renderPlotly({
data <- data %>% filter(key %in% c("date", input$select)) %>% spread(key, value)
if (input$select == c("a", "b", "x")) {
plot_ly(x = ~data$date) %>%
add_lines(y = ~data[, 2], name = input$select[1], line = list(color = "red")) %>%
add_lines(y = ~data[, 3], name = input$select[2], line = list(color = "blue"), yaxis = "y2") %>%
add_lines(y = ~data[, 4], name = input$select[3], line = list(color = "green"), yaxis = "y3") %>%
layout(
yaxis = list(
side = "left"
),
yaxis2 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.02
),
yaxis3 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.04
)
)
} else if (input$select == c("a", "b")) {
plot_ly(x = ~data$date) %>%
add_lines(y = ~data[, 2], name = input$select[1], line = list(color = "red")) %>%
add_lines(y = ~data[, 3], name = input$select[2], line = list(color = "blue"), yaxis = "y2") %>%
layout(
yaxis = list(
side = "left"
),
yaxis2 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.02
),
yaxis3 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.04
)
)
}
})
It is not concise, but may work if there are not a huge number of scenarios.
It also appears you're overwriting your data with the filter call; if your data set is not huge you might skip that step and organize your data outside of the plot call. Otherwise you probably need a reactive function outside of the plot call, that gets called from within the plotting functioning, leaving the original data unmodified.
I'm plotting a financial intra-day time serie on an R-Shiny project using the highcharter package. I'm using the following code for the server part in order to get the output (note that xtsPrices() is a function that returns an xts intraday time-serie):
output$plot <- renderHighchart({
y <- xtsPrices()
highchart() %>%
hc_exporting(enabled = TRUE)%>%
hc_add_series_ohlc(y) %>%
hc_add_theme(hc_theme_538(colors = c("red", "blue", "green"),
chart = list(backgroundColor = "white") ))
})
I read in the documentation that in order to personalize zoom buttons I have to deal with the hc_rangeSelector() function, but I don't understand how to specify them in this R-Shiny environment as shown for the javascript case in Highstock API. In particular - because it's an intra-day time-serie - I would need buttons like "20min", "1h", "3h", "1D", etc.
For intra-day data you can do something like this:
hc <- highchart() %>%
hc_exporting(enabled = TRUE) %>%
hc_add_series_ohlc(y, yAxis = 0, name = "Sample Data", id = "T1",smoothed=TRUE,forced=TRUE,groupPixelWidth=24) %>%
hc_rangeSelector( buttons = list(
list(type = 'all', text = 'All'),
list(type = 'hour', count = 2, text = '2h'),
list(type = 'hour', count = 1, text = '1h'),
list(type = 'minute', count = 30, text = '30m'),
list(type = 'minute', count = 10, text = '10m'),
list(type = 'minute', count = 5, text = '5m')
)) %>%
hc_add_theme(hc_theme_538(colors = c("red", "blue", "green"),chart = list(backgroundColor = "white") ))
hc