#' Folder, file and extension parts of a file name.
#'
#' @param string input character vector
#' @return list of file parts.
#' @import stringr
#' @import jsonlite
#' @import dplyr
#' @import zoo
#' @examples
#' fileparts("filePath")
fileparts <- function(filepath){
parts <- stringr::str_split(filepath,'/')
l <- length(parts[[1]])
if ( l==1 ){
nameExt <- parts[[1]]
name <- stringr::str_replace(nameExt,'\\.\\w+$','')
ext <- stringr::str_match(nameExt,'(?<=\\.)\\w+$')
path <- '' # getwd() more suitable for local apps, but '' for relative paths needed
}else{
nameExt <- parts[[1]][l]
name <- stringr::str_replace(nameExt,'\\.\\w+$','')
ext <- stringr::str_match(nameExt,'(?<=\\.)\\w+$')
path <- paste(parts[[1]][1:l-1],collapse='/')
}
return(c(path,name,ext,nameExt))
}
#' Detection of alone numbers preceded/followed by NAs
#'
#' @param string input character vector
#' @return true/false if an orphan found.
#' @export
#' @examples
#' aloneNumber(c(1,2,3,NA,99,NA))
aloneNumber <- function(x) {
# Returns T for x <- c(1,2,3,NA,99,NA); -> alone 99
xmat <- cbind(x,dplyr::lag(x),dplyr::lead(x))
return( any(rowSums(is.na(xmat))==2 & !is.na(xmat[,1])) )
}
#' Extend pre-allocated arrays if needed
#'
#' @param none
#' @return extended array
addBuffer <- function(x, objInfo) {
if ( !is.null(x[[length(x)]]) ) {
x <- c(x, vector("list",objInfo$buffer))
}
return(x)
}
#' Initializer for plotly graphs (deletes all preceding graphs)
#'
#' @param none.
#' @return none, just pre-allocate in the main workspace
#' @export
#' @examples
#' plotlyIni()
plotlyIni <- function() {
# Plotly containers in global workspace
preallocateBuffer <- 200
assign("plotly_obj_graphs", vector("list",preallocateBuffer), envir = .GlobalEnv)
assign("plotly_obj_paragraphs", vector("list",preallocateBuffer), envir = .GlobalEnv)
assign("plotly_obj_info", list("buffer" = preallocateBuffer,
"maxGraphs" = 1e+05,
"ngraphs" = 0,
"nparagraphs" = 0), envir = .GlobalEnv)
#plotly_obj_viewer not to be reset in plotlyIni(), open reports must be tracked
assign("plotly_obj_graphs_sty", list(grID=c(), style=c()), envir = .GlobalEnv)
assign("plotly_obj_order", vector("numeric",preallocateBuffer), envir = .GlobalEnv)
}
#' Generates HTML text paragraphs
#'
#' @param none.
#' @return none, just pre-allocate in the main workspace
#' @export
#' @examples
#' addText("<h1 style="...">Heading</h1>","<p>Content</p>", list(bullets, style=c("...","...")), style="<for div>")
addText <- function(..., clear = T, style=""){
# Syntax:
# addText("<h1 style="...">Heading</h1>",
# "<p style="...">Content</p>",
# list(bullets, style=c("...","...")),
# style="<for div>")
# Global containers
if (!exists('plotly_obj_info', envir = .GlobalEnv)) {
stop("Initialization failed: Run plotlyIni() first...")
}
txtObj <- get('plotly_obj_paragraphs', envir=.GlobalEnv)
objInfo <- get('plotly_obj_info', envir=.GlobalEnv)
objOrd <- get('plotly_obj_order', envir=.GlobalEnv)
# Preallocate if needed
txtObj <- addBuffer(txtObj, objInfo)
objOrd <- addBuffer(objOrd, objInfo)
# Current graph id
objInfo$nparagraphs <- objInfo$nparagraphs+1
igr <- objInfo$nparagraphs
objOrd[igr+objInfo$ngraphs] <-
igr + objInfo$maxGraphs # to distinguish ordering of graphs/text
# Input objects
objs <- list(...)
nobjs <- length(objs)
# Content glued
buffer_ <- 10
txtGlue <- vector("character", buffer_)
start_ <- 1
tab <- strrep('\t',1)
defaultDIVstyle <- paste0("border-left: 1px solid #ddd;",
"border-right: 1px solid #ddd;",
"margin: 0 auto; ",
"max-width: 800px; ",
"padding: 20px;")
if (clear==T){
defaultDIVstyle <- paste0(defaultDIVstyle, "clear: both; ")
}
if (style!=""){
txtGlue[start_] <- paste0("<div style=\"",
defaultDIVstyle,
style,"\">")
}else{
txtGlue[start_] <- paste0("<div style=\"",
defaultDIVstyle,"\">")
}
start_ <- start_ + 1
# Register traces one by one
for (ii in 1:nobjs){
if (is.character(objs[[ii]])){
# Cases:
# "<h1>...</h1>",
# "<p style="...>...</p>"
if (start_>length(txtGlue)){
txtGlue <- c(txtGlue, vector("character", buffer_))
}
txtGlue[start_] <- paste0(tab, objs[[ii]])
start_ <- start_ + 1
} else if (is.list(objs[[ii]])){
# Bullet points
bullets <- objs[[ii]]
isStyle <- names(bullets)=="style"
if (any(isStyle)){
ulli_style <- bullets[["style"]]
bullets <- bullets[!isStyle]
ulli_cont <- vector("character",length(bullets)+2)
if (length(ulli_style)==1){
# <ul style="...">
ulli_cont[1] <- paste0(tab,"<ul style=\"",ulli_style,"\">")
ulli_cont[2:(length(ulli_cont)-1)] <-
paste0(tab, tab, "<li>",unlist(bullets),"</li>")
}else if (length(ulli_style)==length(bullets)){
# <li style="...">
ulli_cont[1] <- paste0(tab,"<ul>")
ulli_cont[2:(length(ulli_cont)-1)] <-
paste0(tab, tab, "<li style=\"",ulli_style,"\">",unlist(bullets),"</li>")
}else{
stop("Reporting: <li> styling must match number of input bullets...")
}
ulli_cont[length(ulli_cont)] <- paste0(tab,"</ul>")
}else{
ulli_cont <- vector("character",length(bullets)+2)
ulli_cont[1] <- paste0(tab,"<ul>")
ulli_cont[2:(length(ulli_cont)-1)] <-
paste0(tab,tab,"<li>",unlist(bullets),"</li>")
ulli_cont[length(ulli_cont)] <- paste0(tab,"</ul>")
}
# Add to container
end_ <- start_ + length(ulli_cont) -1
if (end_>length(txtGlue)){
txtGlue <- c(txtGlue, vector("character",
max(end_-length(txtGlue), buffer_)) )
}
txtGlue[start_:end_] <- ulli_cont
start_ <- end_ + 1
}else{
stop("Reporting: addText() needs character/list as input...")
}
}
# Closing
if (start_>length(txtGlue)){
txtGlue <- c(txtGlue, "")
}
txtGlue[start_] <- "</div>\n"
# Drop empty pre-allocated space
txtGlue <- txtGlue[1:max(which(txtGlue!=""))]
# Compilation
txtObj[[igr]] <- txtGlue
# Update global containers
assign("plotly_obj_paragraphs", txtObj, envir = .GlobalEnv)
assign("plotly_obj_info", objInfo, envir = .GlobalEnv)
assign("plotly_obj_order", objOrd, envir = .GlobalEnv)
}
#' Generates HTML/plotly code for a new graph
#'
#' @param none.
#' @return none, just pre-allocate in the main workspace
#' @export
#' @examples
#' addGraph(list(periods, values))
#' addGraph(list(periods, values),options)
addGraph <- function(..., # Expected input: ts: list(periods,values)
# line: list(x,y), or just y
# bar: list()
# bubble: list(x,y,[siz])
# -> list per each line
x=c(), # Sequence of periods,
# -> if not default
# -> if not specified inside list(periods,values)
type=c(), # Graph type (line/bubble/bar/custom)
title="",
vline=c(), # Vertical bar separators (list of dates)
legend=c(), # List of line labels
xlabel="",
ylabel="",
colors=c(), # Set of line colors in 'rgba(#,#,#,#)' format
#markerSize="", # Slow rendering - only generated if alone data surrounded by NA exist
#size=6, # Bubble size for bubble type - now solved via data frame input
by="", # Grouping data by existing column, used for 'bubble' type, not available in 'custom' mode
lineWidth=NA, # user-defined value to be tracked
width = 600,
height = 450,
style = "",
clear = F,
######################
#Custom graphs options
layout = list(),
config = list()
){
# Fetch graph container
if (!exists('plotly_obj_graphs', envir = .GlobalEnv)) {
stop("Initialization failed: Run plotlyIni() first...")
}
grObj <- get('plotly_obj_graphs', envir=.GlobalEnv)
objInfo <- get('plotly_obj_info', envir=.GlobalEnv)
objOrd <- get('plotly_obj_order', envir=.GlobalEnv)
# Preallocate if needed
grObj <- addBuffer(grObj, objInfo)
objOrd <- addBuffer(objOrd, objInfo)
# Current graph id
objInfo$ngraphs <- objInfo$ngraphs+1
igr <- objInfo$ngraphs
objOrd[igr+objInfo$nparagraphs] <- igr
assign("plotly_obj_order", objOrd, envir = .GlobalEnv)
# Input objects
objs <- list(...)
nobjs <- length(objs)
# Check for list of multiple objects on input
if (nobjs==1 & is.list(objs[[1]])){
if (!is.null(names(objs[[1]][[1]]))){
nobjs <- length(objs[[1]])
objs <- objs[[1]]
type = "custom"
}
}
# Optional styling on graph <div> level
if (clear==T){
style <- paste0(style,"; clear: both; ")
}
if (style!=""){
# Check if graph width is entered as style="width: x%"
styleFreeOfSpaces <- stringr::str_replace_all(style," +","")
if (stringr::str_detect(styleFreeOfSpaces,"width:\\d+\\%")){
width <- 0 # width obj. not needed any more
}
grObjSty <- get('plotly_obj_graphs_sty', envir=.GlobalEnv)
grObjSty[["grID"]] <- c(grObjSty[["grID"]], igr)
grObjSty[["style"]] <- c(grObjSty[["style"]], style)
assign("plotly_obj_graphs_sty", grObjSty, envir = .GlobalEnv)
}
# Guess graph type if not user-supplied
if (length(type)==0){
if (length(layout)>0 | length(config)>0){
type = "custom"
}else{
guineapig <- objs[[1]]
if (is.ts(guineapig)){
type = "ts"
}else if ((is.list(guineapig))){
if (length(guineapig)==3){
# list(x,y,siz)
type = "bubble"
}else if (length(guineapig)==2){
if (is.character(guineapig[[1]])){
type = "ts"
}else{
type = "line"
}
#}else if (length(guineapig)==1){ -> not needed, most likely numeric input, not a list()
}else{
type = "line"
}
}else if ((is.numeric(guineapig))){
if (length(x)!=0){
if (is.character(x)){
type = "ts"
}else{
type = "line"
}
}else{
type = "line"
}
}else if (is.data.frame(guineapig) | is.matrix(guineapig)){
# if (length(by)!=0){
# # Column name (string) for group by expected here
# type = "bar"
# }else
if (length(x)!=0){
if (is.numeric(x)){
type = "line"
}else{
type = "ts"
}
}else{
type = "line"
}
}
}
}
# Graph type junction
switch(tolower(type),
"ts" ={graphTypeTS(grObj,objInfo,igr,
objs,nobjs,
x,title,vline,legend,xlabel,ylabel,colors,lineWidth,
width,height)},
"line"={graphTypeLine(grObj,objInfo,igr,
objs,nobjs,
x,title,vline,legend,xlabel,ylabel,colors,lineWidth,
width,height)},
"bar"={graphTypeBar(grObj,objInfo,igr,
objs,nobjs,
x,title,legend,xlabel,ylabel,colors,
width,height)},
"bubble"={graphTypeBubble(grObj,objInfo,igr,
objs,nobjs,
title,legend,xlabel,ylabel,colors,by,
width,height)},
"custom"={graphCustom(grObj,objInfo,igr,
objs,nobjs,
x, title, vline, legend, xlabel, ylabel, colors, by, lineWidth,
layout,config,
width,height)},
stop("ts/line/bar/bubble/custom are the only supported graph types...")
)
}
#' Time series graph
#'
#' @param none
#' @return none
graphTypeTS <- function(grObj,objInfo,igr,
objs,nobjs,
x,title,vline,legend,xlabel,ylabel,colors,lineWidth,
width,height){
if (nobjs>1){ # Input has to be list(), list(), ...
# Or ts(), ts(), ts()
if (is.list(objs[[1]])) {
# User-supplied periods info
# -> Input is: data1, data2, data3, x=periods
# -> Need: list(list(periods,data1),list(.),list(.))
if (length(x)!=0) {
objsInput <- objs
for (ii in 1:nobjs) {
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- x # periods
objs[[ii]][[2]] <- as.numeric(objsInput[[ii]][[1]])
}
}
} else if (is.ts(objs[[1]])){
objsInput <- objs
for (ii in 1:nobjs) {
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- zoo::as.Date(time(objsInput[[ii]])) # periods
objs[[ii]][[2]] <- as.numeric(objsInput[[ii]])
}
} else if (is.numeric(objs[[1]])){
if (length(x)==0) {
stop("x=... option is mandatory for time series graph AND input values in numeric format...")
}
objsInput <- objs
for (ii in 1:nobjs) {
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- x # periods
objs[[ii]][[2]] <- as.numeric(objsInput[[ii]]) # should be already numeric
}
}
# !!!-> list(y) together with x=... case is highly unlikely,
# y as numeric would be more natural input, if branch left here for completeness
} else if (nobjs==1 & class(objs[[1]])[1]=="list") { # ...[1] for multi classes
# Only values on input, periods should be separately as x=...
if (length(objs[[1]])==1){
if (length(x)==0){
stop("Periods not specified for time series graph type...")
}else{
objsInput <- objs
objs[[1]] <- vector("list",2)
objs[[1]][[1]] <- x # periods
objs[[1]][[2]] <- as.numeric(objsInput[[1]][[1]])
}
}
} else if (nobjs==1 & class(objs[[1]])[1]!="list") { # Non-list input treatment
# Input can be numeric, matrix, data frame, ts
if (is.ts(objs[[1]])){
periods <- zoo::as.Date(time(objs[[1]]))
objs[[1]] <- as.matrix(objs[[1]]) # because univariate ts not in matrix format
}else if (is.numeric(objs[[1]])){
objs[[1]] <- as.matrix(objs[[1]])
}else if (is.data.frame(objs[[1]])) {
if (ncol(objs[[1]])>1){
if (length(legend)==0){
legend <- colnames(objs[[1]])
}
} else { # Only a single line to draw => title instead
if (title==""){
title <- colnames(objs[[1]]) # Only 1 obj here
}
}
}
obj <- objs[[1]]
nobjs <- ncol(obj)
objs <- vector("list",nobjs)
if (length(x)>0 & !is.ts(obj)){
periods <- x
}
for (ii in 1:nobjs) {
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- periods
objs[[ii]][[2]] <- as.numeric(obj[,ii])
}
} # All possible cases resolution
# Line width(s)
if (is.na(lineWidth)){
lineWidth <- rep(2,nobjs) # Default value
}else if (length(lineWidth)==1){
lineWidth <- rep(lineWidth,nobjs) # User-supplied for all lines
}else if (length(lineWidth)!=nobjs){
stop("plotlyjs: # of supplied lineWidths property does not match # of plotted objs...")
}
# Content creation
objChunk <- 9
# 1 var trace1 = {
# 2 type: "scatter",
# 3 mode: "lines", mode: "lines+markers", mode: "markers",
# 4 name: 'AAPL High',
# 5 x: unpack(rows, 'Date'),
# 6 y: unpack(rows, 'AAPL.High'),
# 7 [OPTIONAL] line: {color: '#17BECF', width: 2},
# 8 [OPTIONAL] marker: {color: 'rgb(128, 0, 128)', size: 8}
# 9 }
JSglue <- vector("character",objChunk*nobjs) # 9 lines per segment
# Obj colors
cols <- genColors(colors, nobjs)
# Legend entries
legend <- legendValidate(legend, nobjs)
# nlgnd <- length(legend)
# if (nlgnd==0) {
# legend <- rep("Series",nobjs)
# legend <- stringr::str_replace(legend,"Series",paste0("Series ",c(1:nobjs)))
# }else if (nlgnd<nobjs) {
# stop("# of legend entries does not match # of drawn objects")
# }
# Register all lines one by one
start_ <- 1
end_ <- objChunk
for (ii in 1:nobjs) {
# Graph type junction
# if (type=="line") {
# Draw markers only if alone values present (for faster rendering)
vals <- objs[[ii]][[2]]
lineSpec <- sprintf("\tline: {color: '%s', width: %g},", cols[ii], lineWidth[ii])
if ( aloneNumber(vals) ) {
objType <- "\tmode: 'lines+markers',"
markerSpec <- sprintf("\tmarker: {color: '%s', size: %g}", cols[ii], lineWidth[ii]) # Marker size matches lineWidth
}else{
objType <- "\tmode: 'lines',"
markerSpec <- ""
}
# }else if (type=="bubble") {
# objType <- "\tmode: 'markers',"
# lineSpec <- ""
# markerSpec <- sprintf("\tmarker: {color: '%s', size: %g}", cols[ii], size)
# }
JSglue[start_:end_] <-
c(sprintf("var gr_%d_trace_%d = {",igr,ii),
"\ttype: 'scatter',",
objType,
sprintf("\tname: '%s',",legend[ii]),
sprintf("\tx: %s,",jsonlite::toJSON(objs[[ii]][[1]])),
sprintf("\ty: %s,",jsonlite::toJSON(vals)),
lineSpec,
markerSpec,
"}")
start_ <- start_+objChunk
end_ <- end_+objChunk
}
# var data = [trace1,trace2,...];
dataObjs <- rep(sprintf("gr_%d_trace_##",igr),nobjs)
dataObjs <- stringr::str_replace(dataObjs,'##',as.character(c(1:nobjs)))
dataObjs <- c(sprintf("var gr_%d_data = [",igr),
c('\t',paste(dataObjs, collapse=',')),
'];')
# Add optional vertical separators
# shapes: [{
# type: 'line',
# x0: '2000-01-11',
# y0: 0,
# x1: '2000-01-11',
# yref: 'paper',
# y1: 1,
# line: {
# color: 'grey',
# width: 1.5,
# dash: 'dot'
# }
# },{
# ...
# }],
if (length(vline)!=0){
# yref: 'paper' wants relative min/max
sh <- "\tshapes: ["
for (ii in 1:length(vline)){
sh <- c(sh,'\t{',
"\t\ttype: 'line',",
sprintf("\t\tx0: '%s',",vline[ii]),
sprintf("\t\ty0: %s,","0"), #gmin),
sprintf("\t\tx1: '%s',",vline[ii]),
"\t\tyref: 'paper',",
sprintf("\t\ty1: %s,","1"),#gmax),
"\t\tline: {",
"\t\t\tcolor: 'grey',",
"\t\t\twidth: 1.5,",
"\t\t\tdash: 'dot'",
"\t\t}",
"\t}")
if (ii<length(vline)){
sh[length(sh)] <- paste0(sh[length(sh)],',')
}else{
sh <- c(sh,'],')
}
}
}else{
sh <- ""
}
# var layout = {
# xaxis: {
# type: 'date',
# title: 'January Weather'
# },
# yaxis: {
# title: 'Daily Mean Temperature'
# },
# shapes: [{...},{...},...],
# title: 'Some title'
# };
layout <- c("var layout = {",
"\txaxis: {",
"\t\ttype: 'date',",
sprintf("\t\ttitle: '%s'",xlabel),
"\t},",
"\tyaxis: {",
sprintf("\t\ttitle: '%s'",ylabel),
"\t},",
sh,
sprintf("\ttitle: '%s',",title),
"\tautosize: false,",
sprintf("\theight: %g,",height),
sprintf("\twidth: %g",width),
"};")
JSglue <- c(JSglue,dataObjs,layout,
sprintf("Plotly.newPlot('gr%d',gr_%d_data,layout,{'displayModeBar': false});",igr,igr))
grObj[[igr]] <- JSglue
# Update global containers
assign("plotly_obj_graphs", grObj, envir = .GlobalEnv)
assign("plotly_obj_info", objInfo, envir = .GlobalEnv)
}
#' Line graph
#'
#' @param none
#' @return none
graphTypeLine <- function(grObj,objInfo,igr,
objs,nobjs,
x,title,vline,legend,xlabel,ylabel,colors,lineWidth,
width,height){
if (nobjs>1){ # Input has to be list(), list(), ...
# or c(), c(), c(), ...
if (is.list(objs[[1]])){
# Already in requested format if list(x,y), list(x,y)
if (length(objs[[1]])==1){ # list(y) format (rarely used)
# User-supplied x info
# -> Input is: y1, y2, y3, x=x
# -> Need: list(list(x,y1),list(.),list(.))
if (length(x)==0) {
x <- c(1:length(objs[[1]][[1]]))
}
objsInput <- objs
for (ii in 1:nobjs) {
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- x # periods
objs[[ii]][[2]] <- as.numeric(objsInput[[ii]][[1]])
}
}
}else if (is.numeric(objs[[1]])){
if (length(x)==0) {
x <- c(1:length(objs[[1]]))
}
objsInput <- objs
for (ii in 1:nobjs) {
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- x # periods
objs[[ii]][[2]] <- as.numeric(objsInput[[ii]]) # should be already numeric
}
}
# y as numeric would be more natural input, if branch left here for completeness
} else if (nobjs==1 & class(objs[[1]])[1]=="list"){ # ...[1] for multi classes
if (length(objs[[1]])==2){
# list(x,y) case
if (length(x)==0){
x <- objs[[1]][[1]] #c(1:length(objs[[1]][[1]]))
}
}else if (length(objs[[1]])==1){
# list(y) together with x=... case is highly unlikely, left here for completeness
if (length(x)==0){
x <- c(1:length(objs[[1]][[1]]))
}
}else{
stop("Unexpected input...")
}
objsInput <- objs
objs[[1]] <- vector("list",2)
objs[[1]][[1]] <- x
objs[[1]][[2]] <- as.numeric(objsInput[[1]][[length(objsInput[[1]])]])
} else if (nobjs==1 & class(objs[[1]])[1]!="list") { # Non-list input treatment
# Input can be numeric, matrix, data frame
if (is.numeric(objs[[1]])){
objs[[1]] <- as.matrix(objs[[1]])
}else if (is.data.frame(objs[[1]])) {
if (ncol(objs[[1]])>1){
if (length(legend)==0){
legend <- colnames(objs[[1]])
}
} else { # Only a single line to draw => title instead
if (title==""){
title <- colnames(objs[[1]]) # Only 1 obj here
}
}
}
obj <- objs[[1]]
if (length(x)==0){
x <- c(1:nrow(obj))
}
nobjs <- ncol(obj)
objs <- vector("list",ncol(obj))
for (ii in 1:nobjs) {
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- x
objs[[ii]][[2]] <- as.numeric(obj[,ii])
}
} # All possible cases resolution
# Line width(s)
if (is.na(lineWidth)){
lineWidth <- rep(2,nobjs) # Default value
}else if (length(lineWidth)==1){
lineWidth <- rep(lineWidth,nobjs) # User-supplied for all lines
}else if (length(lineWidth)!=nobjs){
stop("plotlyjs: # of supplied lineWidths property does not match # of plotted objs...")
}
# Content creation
objChunk <- 9
# 1 var trace1 = {
# 2 type: "scatter",
# 3 mode: "lines", mode: "lines+markers", mode: "markers",
# 4 name: 'AAPL High',
# 5 x: unpack(rows, 'Date'),
# 6 y: unpack(rows, 'AAPL.High'),
# 7 [OPTIONAL] line: {color: '#17BECF', width: 2},
# 8 [OPTIONAL] marker: {color: 'rgb(128, 0, 128)', size: 8}
# 9 }
JSglue <- vector("character",objChunk*nobjs) # 9 lines per segment
# Obj colors
cols <- genColors(colors, nobjs)
# Legend entries
legend <- legendValidate(legend, nobjs)
# nlgnd <- length(legend)
# if (nlgnd==0) {
# legend <- rep("Series",nobjs)
# legend <- stringr::str_replace(legend,"Series",paste0("Series ",c(1:nobjs)))
# }else if (nlgnd<nobjs) {
# stop("# of legend entries does not match # of drawn objects")
# }
# Register all lines one by one
start_ <- 1
end_ <- objChunk
for (ii in 1:nobjs) {
# Graph type junction
# if (type=="line") {
# Draw markers only if alone values present (for faster rendering)
vals <- objs[[ii]][[2]]
lineSpec <- sprintf("\tline: {color: '%s', width: %g},", cols[ii], lineWidth[ii])
if ( aloneNumber(vals) ) {
objType <- "\tmode: 'lines+markers',"
markerSpec <- sprintf("\tmarker: {color: '%s', size: %g}", cols[ii], lineWidth[ii]) # Marker size matches lineWidth
}else{
objType <- "\tmode: 'lines',"
markerSpec <- ""
}
# }else if (type=="bubble") {
# objType <- "\tmode: 'markers',"
# lineSpec <- ""
# markerSpec <- sprintf("\tmarker: {color: '%s', size: %g}", cols[ii], size)
# }
JSglue[start_:end_] <-
c(sprintf("var gr_%d_trace_%d = {",igr,ii),
"\ttype: 'scatter',",
objType,
sprintf("\tname: '%s',",legend[ii]),
sprintf("\tx: %s,",jsonlite::toJSON(objs[[ii]][[1]])),
sprintf("\ty: %s,",jsonlite::toJSON(vals)),
lineSpec,
markerSpec,
"}")
start_ <- start_+objChunk
end_ <- end_+objChunk
}
# var data = [trace1,trace2,...];
dataObjs <- rep(sprintf("gr_%d_trace_##",igr),nobjs)
dataObjs <- stringr::str_replace(dataObjs,'##',as.character(c(1:nobjs)))
dataObjs <- c(sprintf("var gr_%d_data = [",igr),
c('\t',paste(dataObjs, collapse=',')),
'];')
# Add optional vertical separators
# shapes: [{
# type: 'line',
# x0: '2000-01-11',
# y0: 0,
# x1: '2000-01-11',
# yref: 'paper',
# y1: 1,
# line: {
# color: 'grey',
# width: 1.5,
# dash: 'dot'
# }
# },{
# ...
# }],
if (length(vline)!=0){
# yref: 'paper' wants relative min/max
sh <- "\tshapes: ["
for (ii in 1:length(vline)){
sh <- c(sh,'\t{',
"\t\ttype: 'line',",
sprintf("\t\tx0: '%s',",vline[ii]),
sprintf("\t\ty0: %s,","0"), #gmin),
sprintf("\t\tx1: '%s',",vline[ii]),
"\t\tyref: 'paper',",
sprintf("\t\ty1: %s,","1"),#gmax),
"\t\tline: {",
"\t\t\tcolor: 'grey',",
"\t\t\twidth: 1.5,",
"\t\t\tdash: 'dot'",
"\t\t}",
"\t}")
if (ii<length(vline)){
sh[length(sh)] <- paste0(sh[length(sh)],',')
}else{
sh <- c(sh,'],')
}
}
}else{
sh <- ""
}
# var layout = {
# xaxis: {
# type: 'date',
# title: 'January Weather'
# },
# yaxis: {
# title: 'Daily Mean Temperature'
# },
# shapes: [{...},{...},...],
# title: 'Some title'
# };
layout <- c("var layout = {",
"\txaxis: {",
# "\t\ttype: 'date',",
sprintf("\t\ttitle: '%s'",xlabel),
"\t},",
"\tyaxis: {",
sprintf("\t\ttitle: '%s'",ylabel),
"\t},",
sh,
sprintf("\ttitle: '%s',",title),
"\tautosize: false,",
sprintf("\theight: %g,",height),
sprintf("\twidth: %g",width),
"};")
JSglue <- c(JSglue,dataObjs,layout,
sprintf("Plotly.newPlot('gr%d',gr_%d_data,layout,{'displayModeBar': false});",igr,igr))
grObj[[igr]] <- JSglue
# Update global containers
assign("plotly_obj_graphs", grObj, envir = .GlobalEnv)
assign("plotly_obj_info", objInfo, envir = .GlobalEnv)
}
#' Bar graph
#'
#' @param none
#' @return none
graphTypeBar <- function(grObj,objInfo,igr,
objs,nobjs,
x,title,legend,xlabel,ylabel,colors,
width,height){
if (!is.data.frame(objs[[1]]) | nobjs>1){
stop("A single data frame should be passed for 'bar' graphs ")
}
df <- objs[[1]]
# Groupping
# if (length(by)==0){ # No groupping, all data are contributions
nX <- nrow(df)
nY <- ncol(df)
objs <- vector("list",nX)
# Horizontal categories
if (length(x)==0) {
x <- rep("Cat.",nX)
x <- stringr::str_replace(x,"Cat\\.",paste0("Cat. ",c(1:nX)))
}
# Legend entries
if (length(legend)==0){
legend <- colnames(df)
}
if (title=="" & nY==1){
title <- colnames(df) # Only 1 obj here
}
# Populate input lists
for (ii in 1:nY) {
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- x
objs[[ii]][[2]] <- as.numeric(df[,ii])
}
# }
# Content creation
objChunk <- 7
# 1 var trace1 = {
# 2 type: "bar",
# 3 name: 'AAPL High',
# 4 x: unpack(rows, 'Date'),
# 5 y: unpack(rows, 'AAPL.High'),
# 6 [OPTIONAL] marker: {color: ['rgba(204,204,204,1)']}
# 7 }
JSglue <- vector("character",objChunk*nY) # 9 lines per segment
# Obj colors
cols <- genColors(colors, nY)
# if (length(colors)==0){
#
# # Default color set
# cols <- c('rgba(23,190,207,0.9)',
# 'rgba(127,127,127,0.9)',
# 'rgba(244,194,66,0.9)',
# 'rgba(220,130,130,0.9)')
#
# # Generate random colors if more lines needed
# if (length(cols)<nY){
# missing_ <- nY-length(cols)
# randCols <- stringr::str_replace("rgba(#,#,#,0.9)",
# "#",
# as.character(floor(runif(missing_)*255)))
# randCols <- stringr::str_replace(randCols,
# "#",
# as.character(floor(runif(missing_)*255)))
# randCols <- stringr::str_replace(randCols,
# "#",
# as.character(floor(runif(missing_)*255)))
# cols <- c(cols,randCols)
# }
#
# }else{
# if (length(colors)!=nY){
# stop(sprintf(paste0("You have %g objects and provided %g colors",
# " - these numbers must match..."),
# length(colors),nY))
# }
# cols <- colors
# }
# Legend entries
nlgnd <- length(legend)
if (nlgnd<nY) {
stop("# of legend entries does not match # of drawn objects")
}
# Register all contrubutions one by one
start_ <- 1
end_ <- objChunk
for (ii in 1:nY) {
# Draw markers only if alone values present (for faster rendering)
vals <- objs[[ii]][[2]]
# lineSpec <- sprintf("\tline: {color: '%s', width: %g},", cols[ii], lineWidth)
# if ( aloneNumber(vals) ) {
# objType <- "\tmode: 'lines+markers',"
markerSpec <- sprintf("\tmarker: {color: '%s'}", cols[ii])
# }else{
# objType <- "\tmode: 'lines',"
# markerSpec <- ""
# }
JSglue[start_:end_] <-
c(sprintf("var gr_%d_trace_%d = {",igr,ii),
"\ttype: 'bar',",
sprintf("\tname: '%s',",legend[ii]),
sprintf("\tx: %s,",jsonlite::toJSON(objs[[ii]][[1]])),
sprintf("\ty: %s,",jsonlite::toJSON(vals)),
markerSpec,
"}")
start_ <- start_+objChunk
end_ <- end_+objChunk
}
# var data = [trace1,trace2,...];
dataObjs <- rep(sprintf("gr_%d_trace_##",igr),nY)
dataObjs <- stringr::str_replace(dataObjs,'##',as.character(c(1:nY)))
dataObjs <- c(sprintf("var gr_%d_data = [",igr),
c('\t',paste(dataObjs, collapse=',')),
'];')
# var layout = {
# xaxis: {
# type: 'date',
# title: 'January Weather'
# },
# yaxis: {
# title: 'Daily Mean Temperature'
# },
# shapes: [{...},{...},...],
# title: 'Some title'
# };
layout <- c("var layout = {",
"\tbarmode: 'relative',",
"\txaxis: {",
"\t\ttype: 'category',",
sprintf("\t\ttitle: '%s'",xlabel),
"\t},",
"\tyaxis: {",
sprintf("\t\ttitle: '%s'",ylabel),
"\t},",
sprintf("\ttitle: '%s',",title),
"\tautosize: false,",
sprintf("\theight: %g,",height),
sprintf("\twidth: %g",width),
"};")
JSglue <- c(JSglue,dataObjs,layout,
sprintf("Plotly.newPlot('gr%d',gr_%d_data,layout,{'displayModeBar': false});",igr,igr))
grObj[[igr]] <- JSglue
# Update global containers
assign("plotly_obj_graphs", grObj, envir = .GlobalEnv)
assign("plotly_obj_info", objInfo, envir = .GlobalEnv)
}
#' Bubble graph
#'
#' @param none
#' @return none
graphTypeBubble <- function(grObj,objInfo,igr,
objs,nobjs,
title,legend,xlabel,ylabel,colors,by,
width,height){
if (!is.data.frame(objs[[1]]) | nobjs>1){
stop("A single data frame should be passed for 'bar' graphs ")
}
df <- objs[[1]]
# Grouping
# if (length(by)==0){ # No groupping, all data are contributions
nX <- nrow(df)
nY <- ncol(df)
if (by==""){
# Input dimensions
if (!any(nY==c(2,3))){
stop("Bubble chart needs 2 data columns, or 3 including the bubble sizes + 4th column as a grouping variable")
}
objs <- vector("list",1)
# for (ii in 1:nY) {
objs[[1]] <- vector("list",3)
objs[[1]][[1]] <- as.numeric(df[,1])
objs[[1]][[2]] <- as.numeric(df[,2])
if (nY==3){
sizVals <- as.numeric(df[,3])
sizMin <- min(sizVals)
sizMax <- max(sizVals)
if (sizMax>sizMin){
scaleMin <- 6
scaleMax <- 30
sizVals <- round(((sizVals-sizMin)/(sizMax-sizMin))*(scaleMax-scaleMin)+scaleMin)
}else{
sizVals <- rep(11,nX) # Default value
}
objs[[1]][[3]] <- sizVals
}else{
objs[[1]][[3]] <- rep(11,nX) # Default value
}
# }
ngr <- 1
}else{
# Input dimensions
if (!any(nY==c(3,4))){
stop("Bubble chart needs 2 data columns, or 3 including the bubble sizes + 4th column as a grouping variable")
}
cols <- colnames(df)
if (!any(by==cols)){
stop("Grouping variable does not seem to exist in the input data frame...")
}
# Populate lists
groups <- unique(df[,by]) # can be factor here
# df <- df %>% select(-by)
nY <- nY-1
ngr <- length(groups)
objs <- vector("list",ngr)
for (ii in 1:ngr) {
segm <- df %>% filter(UQ(as.name(by))==groups[ii]) %>% select(-UQ(as.name(by)))
nX <- nrow(segm)
objs[[ii]] <- vector("list",2)
objs[[ii]][[1]] <- as.numeric(segm[,1])
objs[[ii]][[2]] <- as.numeric(segm[,2])
if (nY==3){
sizVals <- as.numeric(segm[,3])
sizMin <- min(sizVals)
sizMax <- max(sizVals)
if (sizMax>sizMin){
scaleMin <- 6
scaleMax <- 30
sizVals <- round(((sizVals-sizMin)/(sizMax-sizMin))*(scaleMax-scaleMin)+scaleMin)
}else{
sizVals <- rep(11,nX) # Default value
}
objs[[ii]][[3]] <- sizVals
}else{
objs[[ii]][[3]] <- rep(11,nX) # Default value
}
}
# Legend entries
if (length(legend)==0){
legend <- as.character(groups)
}
}
# # Horizontal categories
# if (length(x)==0) {
# x <- rep("Cat.",nX)
# x <- stringr::str_replace(x,"Cat\\.",paste0("Cat. ",c(1:nX)))
# }
# }
# Content creation
objChunk <- 7
# 1 var trace1 = {
# 2 mode: "markers",
# 3 name: 'AAPL High',
# 4 x: unpack(rows, 'Date'),
# 5 y: unpack(rows, 'AAPL.High'),
# 6 marker: {color: ['rgb(93, 164, 214)'], size: [40, 60, 80, 100]}
# 7 }
JSglue <- vector("character",objChunk*ngr) # 9 lines per segment
# Obj colors
cols <- genColors(colors, ngr)
# Legend entries
nlgnd <- length(legend)
if (nlgnd<ngr) {
stop("# of legend entries does not match # of drawn objects")
}
# Register all contrubutions one by one
start_ <- 1
end_ <- objChunk
for (ii in 1:ngr) {
# Draw markers only if alone values present (for faster rendering)
xvals <- objs[[ii]][[1]]
yvals <- objs[[ii]][[2]]
zvals <- objs[[ii]][[3]]
# lineSpec <- sprintf("\tline: {color: '%s', width: %g},", cols[ii], lineWidth)
# if ( aloneNumber(vals) ) {
# objType <- "\tmode: 'lines+markers',"
markerSpec <- sprintf("\tmarker: {color: '%s', size: %s}", cols[ii], as.character(jsonlite::toJSON(zvals)))
# }else{
# objType <- "\tmode: 'lines',"
# markerSpec <- ""
# }
JSglue[start_:end_] <-
c(sprintf("var gr_%d_trace_%d = {",igr,ii),
"\tmode: 'markers',",
sprintf("\tname: '%s',",legend[ii]),
sprintf("\tx: %s,",jsonlite::toJSON(xvals)),
sprintf("\ty: %s,",jsonlite::toJSON(yvals)),
markerSpec,
"}")
start_ <- start_+objChunk
end_ <- end_+objChunk
}
# var data = [trace1,trace2,...];
dataObjs <- rep(sprintf("gr_%d_trace_##",igr),ngr)
dataObjs <- stringr::str_replace(dataObjs,'##',as.character(c(1:ngr)))
dataObjs <- c(sprintf("var gr_%d_data = [",igr),
c('\t',paste(dataObjs, collapse=',')),
'];')
# var layout = {
# xaxis: {
# type: 'date',
# title: 'January Weather'
# },
# yaxis: {
# title: 'Daily Mean Temperature'
# },
# shapes: [{...},{...},...],
# title: 'Some title'
# };
layout <- c("var layout = {",
"\tbarmode: 'relative',",
"\txaxis: {",
# "\t\ttype: 'date',",
sprintf("\t\ttitle: '%s'",xlabel),
"\t},",
"\tyaxis: {",
sprintf("\t\ttitle: '%s'",ylabel),
"\t},",
sprintf("\ttitle: '%s',",title),
"\tautosize: false,",
sprintf("\theight: %g,",height),
sprintf("\twidth: %g",width),
"};")
JSglue <- c(JSglue,dataObjs,layout,
sprintf("Plotly.newPlot('gr%d',gr_%d_data,layout,{'displayModeBar': false});",igr,igr))
grObj[[igr]] <- JSglue
# Update global containers
assign("plotly_obj_graphs", grObj, envir = .GlobalEnv)
assign("plotly_obj_info", objInfo, envir = .GlobalEnv)
}
#' Custom graph
#'
#' @param none
#' @return none
graphCustom <- function(grObj,objInfo,igr,
objs,nobjs,
x, title, vline, legend, xlabel, ylabel, colors, by, lineWidth,
layout,config,
width,height){
# Grouping by column banned
if (by!=""){
stop("plotlyjs: 'by' option does not work in 'custom' mode")
}
# User defined options
# Trace params
if (!is.null(x)){
for (iobj in 1:nobjs){
if (!('x' %in% names(objs[[iobj]]))){
objs[[iobj]]$x <- x
}
}
}
if (!is.null(legend)){
legend <- legendValidate(legend, nobjs)
for (iobj in 1:nobjs){
objs[[iobj]]$name <- legend[iobj]
}
}
if (!is.null(colors)){
cols <- genColors(colors, nobjs)
for (iobj in 1:nobjs){
objs[[iobj]]$line$color <- cols[iobj]
}
}
if (!is.na(lineWidth)){
if (length(lineWidth)==1){
lineWidth <- rep(lineWidth,nobjs)
}
for (iobj in 1:nobjs){
objs[[iobj]]$line$width <- lineWidth[iobj]
}
}
# Layout param
if (!is.list(layout)){
stop("Plotly layout param must be a list...")
}
if (!('hovermode' %in% names(layout))){
layout$hovermode <- "closest"
}
if (title!=""){
layout$title <- title
}
if (xlabel!=""){
layout$xaxis$title <- xlabel
}
if (ylabel!=""){
layout$yaxis$title <- ylabel
}
if (!is.numeric(width) | !is.numeric(height)){
stop("Plotly graph dimensions - numeric input needed...")
}
layout$autosize <- T # Used to be F, but width: 100% did not work
if (width>0){ # Can be overruled by style="width: 100%",
# in that case 'width' parameter is set to 0
layout$width <- width
}
layout$height <- height
# Config param
if (length(config)==0){
config <- list(displayModeBar = F)
}else{
if (is.list(config)){
if ( !('displayModeBar' %in% names(config)) ){
config$displayModeBar <- F
}
#config <- processOpts(config, 2)
}else{
stop("Plotly config param needs to be in list(field: val, ...) format")
}
}
# Content glue
buffer_ <- 100
trGlue <- vector("character", buffer_)
start_ <- 1
# Register traces one by one
for (ii in 1:nobjs){
trGlue[start_] <- sprintf("var gr_%d_trace_%d = {",igr,ii)
start_ <- start_ + 1
#opts <- names(objs[[ii]])
res <- processOpts(objs[[ii]], 1)
end_ <- start_ + length(res) -1
if (end_>length(trGlue)){
trGlue <- c(trGlue,
vector("character", max(end_-length(trGlue), buffer_)) )
}
trGlue[start_:end_] <- res
trGlue[end_+1] <- '}'
start_ <- end_+2
}
# Drop empty pre-allocated space
trGlue <- trGlue[1:max(which(trGlue!=""))]
# [2] Data object
# var data = [trace1,trace2,...];
dataObjs <- rep(sprintf("gr_%d_trace_##",igr),nobjs)
dataObjs <- stringr::str_replace(dataObjs,'##',as.character(c(1:nobjs)))
dataObjs <- c(sprintf("var gr_%d_data = [",igr),
c('\t',paste(dataObjs, collapse=',')),
'];')
# [3] Layout
# Add optional vertical separators
if (length(vline)!=0){
# yref: 'paper' wants relative min/max
sh <- "\tshapes: ["
for (ii in 1:length(vline)){
sh <- c(sh,'\t{',
"\t\ttype: 'line',",
sprintf("\t\tx0: '%s',",vline[ii]),
sprintf("\t\ty0: %s,","0"), #gmin),
sprintf("\t\tx1: '%s',",vline[ii]),
"\t\tyref: 'paper',",
sprintf("\t\ty1: %s,","1"),#gmax),
"\t\tline: {",
"\t\t\tcolor: 'grey',",
"\t\t\twidth: 1.5,",
"\t\t\tdash: 'dot'",
"\t\t}",
"\t}")
if (ii<length(vline)){
sh[length(sh)] <- paste0(sh[length(sh)],',')
}else{
sh <- c(sh,'],')
}
}
}else{
sh <- ""
}
# layout is never empty, at least height/width exist
layGlue <- c("var layout = {",
sh, # better listed as first, overruling on next line
processOpts(layout, 1),
"}")
# [4] Config options
# -> config is never empty, at least displayModeBar exists
confOpt <- c("var config = {",
processOpts(config, 1),
"}")
# Compilation
grObj[[igr]] <- c(trGlue,dataObjs,layGlue,confOpt,
sprintf("Plotly.newPlot('gr%d',gr_%d_data,layout,config);",igr,igr))
# Update global containers
assign("plotly_obj_graphs", grObj, envir = .GlobalEnv)
assign("plotly_obj_info", objInfo, envir = .GlobalEnv)
}
#' list() -> JSON generator (mainly for graph trace defs)
#'
#' @param none
#' @return JSON structure
processOpts <- function(input, level){
# list() case <empty>, e.g. for rangeslider: {} (no params needed inside)
if (length(input)==0){
# !!! last time we had rangeslider: [{}], including [], perhaps ok
segmGlue <- ""
return(segmGlue)
}
# Main part
buffer_ <- 10
segmGlue <- vector("character", buffer_)
start_ <- 1
tabs <- strrep('\t',level)
opts <- names(input)
# Treat shapes/annotations in [{...},{...}] format
if ( all(sapply(input, is.list)) & is.null(opts) ){
# input = list of lists without names
# output = [{...},{...}]
for (ii in 1:length(input)){
# list closing
if (ii==length(input)){
endL <- '' # \n not necessary for writeLines()
}else{
endL <- '},{'
}
res <- processOpts(input[[ii]], level+1)
end_ <- start_ + length(res) # including }] line
if (end_>length(segmGlue)){
segmGlue <- c(segmGlue,
vector("character",
max(end_-length(segmGlue),buffer_)) )
}
segmGlue[(start_):(start_+length(res)-1)] <- res
segmGlue[start_+length(res)] <- paste0(tabs, endL)
start_ <- start_ +length(res)+1
}
# Drop empty pre-allocated space
segmGlue <- segmGlue[1:max(which(segmGlue!=""))]
return(segmGlue)
}
for (iopt in opts){
# Line ending
if (iopt==opts[length(opts)]){
endL <- '' # \n not necessary for writeLines()
}else{
endL <- ','
}
# Value processing
val <- get(iopt, input)
if (is.list(val)){
res <- processOpts(val, level+1)
end_ <- start_ + length(res) +1
if (end_>length(segmGlue)){
segmGlue <- c(segmGlue,
vector("character",
max(end_-length(segmGlue),buffer_)) )
}
if ( all(sapply(val, is.list)) & is.null(names(val)) ){
# input was list of lists without names
# output = [{...},{...}]
segmGlue[start_] <- paste0(tabs, iopt, ": [{")
segmGlue[(start_+1):(start_+length(res))] <- res
segmGlue[start_+length(res)+1] <- paste0(tabs, '}]', endL)
}else{
# Usual cases {...}
segmGlue[start_] <- paste0(tabs, iopt, ": {")
segmGlue[(start_+1):(start_+length(res))] <- res
segmGlue[start_+length(res)+1] <- paste0(tabs, '}', endL)
}
start_ <- start_ +length(res)+2
}else{
if(any( (iopt==c('x','y','z')) & level==1) | length(val)>1 ){
# 'x' in shapes/annotations must have scalar values, not jsonized (solved by level==1)
# c(1,2,3) -> [1,2,3]
val <- jsonlite::toJSON(val)
}else if (is.character(val) | is.Date(val)){
# if (substr(val,1,1)=='#'){
# # #colors -> colors[ii]
# s <- substr(val,2,nchar(val))
# s <- paste0(toupper(substring(s, 1,1)), substring(s, 2))
# paste0("gen",s)
# val <- get(substr(val,2,nchar(val)))[tracenum]
# }
val <- paste0("\"", val, "\"")
}else if (is.logical(val)){
# true/false lower case needed, no quotes
val <- tolower(as.character(val))
}
if (start_>length(segmGlue)){
segmGlue <- c(segmGlue, vector("character", buffer_))
}
segmGlue[start_] <- paste0(tabs, iopt, ": ", val, endL)
start_ <- start_ + 1
}
}
# Drop empty pre-allocated space
segmGlue <- segmGlue[1:max(which(segmGlue!=""))]
return(segmGlue)
}
#' Compilation of HTML/plotly document
#'
#' @param all optional.
#' @return none, modifies the given HTML template file.
#' @export
#' @examples
#' plotlyCompile()
plotlyCompile <- function(reportFile="tmp.html",
libFile="path/to/file/plotly.min.js",
lightWeight = F,
css = "",
font = "",
name = "", #"To edit: Some report name",
reopen = F,
debug = F){
# Fetch graph container
if (!exists('plotly_obj_graphs', envir = .GlobalEnv)) {
stop("Report compilation failed: Run plotlyIni() first, then add some graphs using addGraph()...")
}
grObj <- get('plotly_obj_graphs', envir=.GlobalEnv)
txtObj <- get('plotly_obj_paragraphs', envir=.GlobalEnv)
objInfo <- get('plotly_obj_info', envir=.GlobalEnv)
grObjSty <- get('plotly_obj_graphs_sty', envir=.GlobalEnv)
objOrd <- get('plotly_obj_order', envir=.GlobalEnv)
nobj <- objInfo$ngraphs + objInfo$nparagraphs
if (nobj==0){
stop("plotlyjs: No objects on input, nothing to compile...")
}
# Cut off preallocated space
if (objInfo$ngraphs>0){
grObj <- grObj[1:objInfo$ngraphs]
}else{
grObj <- list("// No graph objects on input")
}
objOrd <- objOrd[1:nobj]
###############################
# Generate HTML div tags
# [1] Graphs
if (objInfo$ngraphs>0){
grDivs <- stringr::str_replace("<div id='#'></div>",
"#",
paste0('gr',c(1:objInfo$ngraphs)))
# User-imposed style for some graphs
if (length(grObjSty[["grID"]])!=0){
grDivs[grObjSty[["grID"]]] <-
stringr::str_replace(grDivs[grObjSty[["grID"]]],
"><",
paste0(" style=\"",grObjSty[["style"]],"\"><"))
}
}else{
grDivs <- c()
}
# browser()
# [2] Text fields
#ord <- c(1,2,10001,3,4)
if (objInfo$nparagraphs>0){
txtObj <- txtObj[1:objInfo$nparagraphs]
vals <- c(as.list(grDivs),txtObj)
ids <- c(1:length(grDivs), (1:length(txtObj))+objInfo$maxGraphs)
}else{
vals <- as.list(grDivs) # some graphs exist for sure, 0 treated before
ids <- c(1:length(grDivs))
}
allDivs <- unlist(vals[match(objOrd,ids)])
# Parse input file name
out <- fileparts(reportFile)
report_folder <- out[1]
fileName <- out[2]
if (report_folder==''){
report_folder = getwd()
}
if (!dir.exists(report_folder)){
dir.create(report_folder)
}
# Link to the plotlyJS library
if (lightWeight==F) {
# Create the regular folder/file structure
# + use local plotly library
# Library folder
if (!dir.exists(paste0(report_folder,"/js"))){
dir.create(paste0(report_folder,"/js"))
if (libFile!="path/to/file/plotly.min.js"){
localLibName <- fileparts(libFile)[4]
file.copy(libFile,
paste0(report_folder,"/js/",localLibName))
}else{
download.file("https://cdn.plot.ly/plotly-latest.min.js",
paste0(report_folder,"/js/plotly-latest.min.js"))
}
# Pick the JS library file from the folder contents
contents <- list.files(paste0(report_folder,"/js"))
JScontents <- stringr::str_match(contents,'plotly.*?\\.js')
pickedFile <- JScontents[!is.na(JScontents)][1]
libLink <- paste0("<script src=\"js/",pickedFile,"\"></script>")
}else{
# Check if any plotly lib inside the existing js folder
contents <- list.files(paste0(report_folder,"/js"))
JScontents <- stringr::str_match(contents,'plotly.*?\\.js')
if ( all(is.na(JScontents)) ){
download.file("https://cdn.plot.ly/plotly-latest.min.js",
paste0(report_folder,"/js/plotly-latest.min.js"))
libLink <- paste0("<script src=\"js/plotly-latest.min.js\"></script>")
}else{
pickedFile <- JScontents[!is.na(JScontents)][1]
libLink <- paste0("<script src=\"js/",pickedFile,"\"></script>")
}
}
}else{ # light-weight version (single HTML output file)
libLink <- "<script src=\"https://cdn.plot.ly/plotly-latest.min.js\"></script>"
}
##############
# Default CSS styling
# Graphs
CSS <- "div[id^=\"gr\"] {float: left}"
# Debugging
if (debug==T){
CSS <- c(CSS,"\n\t\t/* Debugging */","div {border: 1px solid red}\n")
}
# CSS - loader
CSS <- c(CSS,"#loader-back {
position: fixed;
height: 100%;
width: 100%;
z-index: 1;
}
#loader {
position: fixed;
top:35%; left:50%;
margin-top: -60px;
margin-left: -80px;
z-index: 2;
/*border: 16px solid #f3f3f3;*/
border-radius: 80%;
border-top: 26px solid #3498db;
border-bottom: 26px solid #3a91ab;
border-left: 6px solid #3a91ab;
border-right: 6px solid #3a91ab;
width: 160px;
height: 120px;
-webkit-animation: spin 3s linear infinite;
animation: spin 3s linear infinite;
}
/* Safari */
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#loader-msg {
position: fixed;
top:35%; left:50%;
text-align: center;
vertical-align: middle;
line-height: 172px;
width: 172px;
height: 172px;
/*border: 1px solid red;*/
margin-top: -60px;
margin-left: -80px;
z-index: 3;
}")
# External fonts
# - always to be downloaded, base64 encoding currently not implemented
# - can also be copied <link href=...> from Google APIs
if (is.numeric(font)){
if (font==1){
#font <- "<link href=\"https://fonts.googleapis.com/css?family=Open+Sans&display=swap\" rel=\"stylesheet\">"
#family <- "font-family: \"Open Sans\", sans-serif;"
font <- "<link href=\"https://fonts.googleapis.com/css?family=PT+Sans&display=swap\" rel=\"stylesheet\">"
family <- "font-family: 'PT Sans', sans-serif;"
}else if (font==2){
font <- "<link href=\"https://fonts.googleapis.com/css?family=Roboto+Slab&display=swap\" rel=\"stylesheet\">"
family <- "font-family: 'Roboto Slab', serif;"
}else{
stop("Font specification - Predefined values: 1, 2 available only")
}
# Update styling
CSS <- c(CSS, paste0("div {", family, "}"))
}else if (is.list(font)){
if (all(c('font','family') %in% names(font))){
family <- font$family
font <- font$font
# Update styling
CSS <- c(CSS, paste0("div {font-family: ",
stringr::str_replace(family,"font-family: ",""), "}"))
}else{
stop("Font specification: Example input - list(font=\"<link href=...fonts google APIs>\", family=\"font-family: 'Roboto Slab', serif;\")")
}
}else if (font==""){
font <- "\t\t<!-- Default fonts used -->\n"
}else{
stop("Font specification: numeric input (1,2,...) or list input needed")
}
# User-defined CSS can overwrite default setup
CSS <- c(CSS,paste0("\t\t",css))
# Background image
if (name!=""){
headerPart <- c("<!-- Header -->",
"<div id='header' style=\"margin-left: 5px;",
paste0("\t\t\tbackground-repeat: no-repeat; background-size: 100% 100%;\">"),
paste0("\t<h1 style=\"margin-top: 5px; margin-bottom: 5px\">", name, "</h1>"),
paste0("\t<p style=\"font-size: 10px; margin-top: 5px;\">Report creation timestamp: ",Sys.time(),"</p>"),
"</div>\n")
backgroundPic <- paste0("\ndocument.getElementById('header').style.backgroundImage = \"url('", encodedBackground(), "')\";")
}else{
# Do not show the background image, just the creation time stamp
headerPart <- c("<!-- Header -->",
"<div id='header' style=\"margin-left: 5px;\">",
paste0("\t<p style=\"font-size: 10px; margin-top: 5px;\">Report creation timestamp: ",Sys.time(),"</p>"),
"</div>\n")
backgroundPic <- "\n"
}
# Rewrite the report file from scratch
fileConn<-file(reportFile)
writeLines(c("<!DOCTYPE html>",
"<html>", # '<html lang="en">'
"\t<head>",
paste0("\t\t", libLink),
"\t\t<!-- Loader -->",
"\t\t<script language=\"javascript\" type=\"text/javascript\">",
"\t\t\t\twindow.addEventListener('load', function () {",
"\t\t\t\t\tdocument.getElementById(\"loader\").style.display='none';",
"\t\t\t\t\tdocument.getElementById(\"loader-msg\").style.display='none';",
"\t\t\t\t\tdocument.getElementById(\"loader-back\").style.display='none';",
"\t\t\t\t\t//document.getElementById(\"content\").style.display='block';// not needed unless display 'none' initially",
"\t\t\t\t}, false);",
"\t\t</script>",
"\t\t<!-- Font -->",
paste0("\t\t", font),
"\t</head>",
"<body>",
"\t<style>",
paste0("\t\t", CSS),
"\t</style>\n",
"<div id=\"loader\"></div>",
"<div id=\"loader-msg\">Loading...</div>",
"<div id=\"loader-back\"></div>\n",
# Main content
# 'none' needed for loader (hides everything initially)
# but it does not render width: 100% well
# display: block preferred even though plain HTML parts are visible while loading
"<div id=\"content\" style=\"display: block;\">",
# "<!-- Header -->",
# "<div id='header' style=\"margin-left: 5px;",
# paste0("\t\t\tbackground-repeat: no-repeat; background-size: 100% 100%;\">"),
# paste0("\t<h1 style=\"margin-top: 5px; margin-bottom: 5px\">", name, "</h1>"),
# paste0("\t<p style=\"font-size: 10px; margin-top: 5px;\">Report creation timestamp: ",Sys.time(),"</p>"),
# "</div>\n",
headerPart,
"<!-- Page content -->",
allDivs,
"\n</div> <!-- 'content' closing -->",
"</body>",
#paste0("\t<script src=\"",JSfileInject,"\"></script>"),
"<script>",
unlist(grObj),
#paste0("\ndocument.getElementById('header').style.backgroundImage = \"url('", backgroundPic, "')\";"),
backgroundPic,
"</script>",
"</html>"), fileConn)
close(fileConn)
# Console info
if (lightWeight==F) {
cat(" ----> Report created in fast mode...\n")
cat(" ",reportFile,'\n')
}else{
cat(" ----> Lightweight HTML report created...\n")
cat(" ",reportFile,'\n')
}
# Open up the report in a web browser only if not yet opened
openReport(reportFile, reopen)
}
#' Automatic report opening
#'
#' @param none
#' @return none
openReport <- function(reportFile, reopen){
# Open up the report in browser only if not done previously
if (!any("plotly_obj_viewer"==ls(envir = .GlobalEnv))){
# Not everyone uses Rstudio
if (Sys.getenv("RSTUDIO")=="1"){
rstudioapi::viewer(reportFile)
}else{
browseURL(reportFile)
}
# Mark the current report file as already opened
assign("plotly_obj_viewer", reportFile, envir = .GlobalEnv)
}else{
# Opened reports
reports <- get('plotly_obj_viewer', envir = .GlobalEnv)
# Current report not yet opened
if (reopen==T || !any(reportFile==reports)){
# Not everyone uses Rstudio
if (Sys.getenv("RSTUDIO")=="1"){
rstudioapi::viewer(reportFile)
}else{
browseURL(reportFile)
}
reports <- c(reports,reportFile)
assign("plotly_obj_viewer", reports, envir = .GlobalEnv)
}
}
}
#' Compilation of HTML/plotly document
#'
#' @param numeric.
#' @return none, modifies the given HTML template file.
#' @export
#' @examples
#' figure(c(1:5))
figure <- function(..., # Expected input: ts: list(periods,values)
# line: list(x,y), or just y
# bar: list()
# bubble: list(x,y,[siz])
# -> list per each line
x=c(), # Sequence of periods,
# -> if not default
# -> if not specified inside list(periods,values)
type=c(), # Graph type (line/bubble/bar)
title="",
vline=c(), # Vertical bar separators (list of dates)
legend=c(), # List of line labels
xlabel="",
ylabel="",
colors=c(), # Set of line colors in 'rgba(#,#,#,#)' format
#markerSize="", # Slow rendering - only generated if alone data surrounded by NA exist
#size=6, # Bubble size for bubble type
by="", # Grouping data by existing column, used for 'bubble' type
lineWidth=2,
width = 600,
height = 450,
######################
#Custom graphs options
layout = list(),
config = list(),
style = "",
####################
#Compilation options
reportFile="tmp.html",
libFile="path/to/file/plotly.min.js",
lightWeight = F,
name = "",
css = "",
font="",
reopen = F,
debug = F){
plotlyIni()
addGraph(...,x=x,
type=type,
title=title,
vline=vline,
legend=legend,
xlabel=xlabel,
ylabel=ylabel,
colors=colors,
by=by,
lineWidth=lineWidth,
width=width,
height=height,
layout=layout,
config=config,
style=style)
plotlyCompile(reportFile=reportFile,
libFile=libFile,
lightWeight = lightWeight,
name=name,
css=css,
font=font,
reopen=reopen,
debug=debug)
}
#' Colors generator for given number of objects
#'
#' @param none
#' @return none
genColors <- function(colors, ngr){
if (length(colors)==0){
# Default color set
cols <- c('rgba(23,190,207,0.9)',
'rgba(127,127,127,0.9)',
'rgba(244,194,66,0.9)',
'rgba(220,130,130,0.9)')
# Generate random colors if more lines needed
if (length(cols)<ngr){
missing_ <- ngr-length(cols)
randCols <- stringr::str_replace("rgba(#,#,#,0.9)",
"#",
as.character(floor(runif(missing_)*255)))
randCols <- stringr::str_replace(randCols,
"#",
as.character(floor(runif(missing_)*255)))
randCols <- stringr::str_replace(randCols,
"#",
as.character(floor(runif(missing_)*255)))
cols <- c(cols,randCols)
}
}else{
if (length(colors)!=ngr){
stop(sprintf(paste0("You have %g objects and provided %g colors",
" - these numbers must match..."),
length(colors),ngr))
}
cols <- colors
}
return(cols)
}
#' Legend shape validation
#'
#' @param none
#' @return none
legendValidate <- function(legend, nobjs){
nlgnd <- length(legend)
if (nlgnd==0) {
legend <- rep("Series",nobjs)
legend <- stringr::str_replace(legend,"Series",paste0("Series ",c(1:nobjs)))
}else if (nlgnd<nobjs) {
stop("# of legend entries does not match # of drawn objects")
}
return(legend)
}
#<eof>
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.