I'm new here, so hopefully I did a good job putting together a good question!
What the reproducible code will produce is a map of North Carolina, broken out evenly to four geographically "even" regions. Imagine this is being used by a Sales Manager who is assigning territory to his salespeople.
What this does now: Right now, this map does the following: Allows you to select a region, which then creates two tables. The first is just a straight data dump of the accompanying features associated with that county. The second table then groups that data together to produce sums of the data for each region.
What I want this to do: Let's say the person using this wants to assign new territory. Each of those territories are assigned a color. A is red, B is blue, C is green, and D is yellow. So they select an input button for "A" and then he begins to click on counties, which turn red, and all do all the table aggregations at the bottom. Once they're done with that, they select "B", and so on. So then the table at the bottom looks like:
+-----------+--------------------+--------------------+
| Territory | Leads | Sales |
+-----------+--------------------+--------------------+
| A | selected agg value | selected agg value |
| B | selected agg value | selected agg value |
| C | selected agg value | selected agg value |
| D | selected agg value | selected agg value |
+-----------+--------------------+--------------------+
Does that make sense?
library(tigris)
library(mapview)
library(mapedit)
library(leaflet)
library(dplyr)
library(DT)
north_carolina <- counties("north carolina") %>% st_as_sf() %>% arrange(INTPTLON, INTPTLAT) %>% dplyr::select(NAMELSAD, geometry) %>% rename(county_name = NAMELSAD) %>%
mutate(territory = rep(letters[1:4], each = 25), leads = sample(100:1000, 100, replace = TRUE), sales = sample(100:1000, 100, replace = TRUE))
ui <- fluidPage(
h3("Map"),
selectModUI(id = "map_select"),
# Datatable Output
h3("Table"),
dataTableOutput(outputId = "BaseTable"),
h3("Reactive Output"),
dataTableOutput(outputId = "ReactTable")
)
server <- function(input, output) {
leafmap <- reactive({leaflet() %>%
addProviderTiles(providers$Stamen.Toner) %>%
addPolygons(data = north_carolina, fillOpacity = "red",
fillColor = "grey",
weight = 5,
opacity = 5,
color = "black") %>%
leafem::addFeatures(data=north_carolina,label = ~htmltools::htmlEscape(territory),
layerId = ~seq_len(length(st_geometry(north_carolina))))
})
selectMod <- function(input, output, session, leafmap,
styleFalse = list(fillOpacity = 0.2, weight = 1, opacity = 0.4),
styleTrue = list(fillOpacity = 0.7, weight = 3, opacity = 0.7))
{
print("*** custom selectMod")
output$map <- leaflet::renderLeaflet({
mapedit:::add_select_script(leafmap, styleFalse = styleFalse, styleTrue = styleTrue,
ns = session$ns(NULL))
})
id <- "mapedit"
select_evt <- paste0(id, "_selected")
df <- data.frame()
selections <- reactive({
id <- as.character(input[[select_evt]]$id)
if (length(df) == 0) {
# Initial case, first time module is called.
# Switching map, i.e. subsequent calls to the module.
# Note that input[[select_evt]] will always keep the last selection event,
# regardless of this module being called again.
df <<- data.frame(id = character(0), selected = logical(0),
stringsAsFactors = FALSE)
} else {
loc <- which(df$id == id)
if (length(loc) > 0) {
df[loc, "selected"] <<- input[[select_evt]]$selected
} else {
df[nrow(df) + 1, ] <<- c(id, input[[select_evt]]$selected)
}
}
return(df)
})
return(selections)
}
rval <- reactiveValues(
sel = reactive({}),
selectnum = NULL,
base_table = north_carolina %>%
st_set_geometry(NULL) %>%
dplyr::slice(0)
)
# Create selectMod
observeEvent(leafmap(),
rval$sel <- callModule(selectMod, "map_select", leafmap())
)
# Subset the table based on the selection
observeEvent(rval$sel(), {
# The select module returns a reactive
gs <- rval$sel()
# Filter for the county data
rval$selectnum <- as.numeric(gs[which(gs$selected == TRUE), "id"])
rval$base_table <- north_carolina %>%
st_set_geometry(NULL) %>%
dplyr::slice(rval$selectnum)
rval$react_table <- rval$base_table %>% group_by(territory) %>% summarise(leads = sum(leads), sales = sum(sales))
})
# Create a datatable
output$BaseTable <- renderDataTable({
datatable(rval$base_table, options = list(scrollX = TRUE))
})
output$ReactTable <- renderDataTable({
datatable(rval$react_table)
})
}
Related
I have this shiny app that generates a network graph from a df.
library(shiny)
library(dplyr)
library(tibble)
library(networkD3)
ui <- fluidPage(
sidebarPanel(
fluidRow(selectInput("nos","Mínimo de orientações",c(1:10),selected=c(1)))
),
fluidRow(simpleNetworkOutput(
"redes", width = "100%", height = "800px"
))
)
server <- function(input, output, session) {
df_orientadores <- data.frame(orientador=c("Chet Baker","Bill Evans","Miles Davis","Miles Davis","Dizzy Gillespie","Miles Davis"),
autor=c("Clifford Brown","Freddie Hubbard","Kenny Dorham","Kenny Burrell","Arturo Sandoval","Goku"))
output$redes <- renderSimpleNetwork({
sources <- df_orientadores %>%
select(orientador) %>%
dplyr::rename(label = orientador)
destination <- df_orientadores %>%
select(autor) %>%
dplyr::rename(label = autor)
nodes <- full_join(sources, destination, by = "label")
nodes <- nodes %>% group_by(label) %>% count(label) %>% rename(freq=n)
nodes <- nodes %>% rowid_to_column("id")
nodes$peso <- ((nodes$freq)^3)
orientacoes_network <- df_orientadores %>%
group_by(orientador, autor) %>%
dplyr::summarise(weight = n()) %>%
ungroup()
edges <- orientacoes_network %>%
left_join(nodes, by = c("orientador" = "label")) %>%
dplyr::rename(from = id)
edges <- edges %>%
left_join(nodes, by = c("autor" = "label")) %>%
dplyr::rename(to = id)
edges <- select(edges, from, to, weight)
nodes_d3 <- mutate(nodes, id = id - 1)
edges_d3 <- mutate(edges, from = from - 1, to = to - 1)
filtro_nos <- nodes_d3
edges_d3$value <- 1
forceNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to",
NodeID = "label", Group = "id", Value = "value",
opacity = 1, fontSize = 20, zoom = TRUE, Nodesize = "peso",
arrows = TRUE)
})
}
shinyApp(ui, server)
I want to update the graph by the minimum number of nodes (described as freq in the nodes_d3 dataframe) that the user chooses (on the input$nos)
I've tried filtering the nodes_d3 and the edges_d3 by the number of frequencies but it return the error Warning: Error in $<-.data.frame: replacement has 1 row, data has 0 [No stack trace available]
any ideas how to do it?
I've tried using reactiveValues as well, but it wouldn't do. I don't know if in this kind of situation I have to subset the original dataframe and generate the network, or simply subsetting the dfs used in the forcenetwork (which I think I did but still didn't work.)
Once you've created your data, you need to filter both the edges_d3 and the nodes_d3 data frames, and then you need to re-adjust the from and to values in the filtered edges_d3 data frame to reflect the new positions of the nodes they refer to in the nodes_d3 data frame.
# determine the nodes that have at least the minimum freq
nodes_d3_min_freq <-
nodes_d3 %>%
filter(freq >= input$nos)
# filter the edge list to contain only links to or from the nodes that have
# the minimum or more freq
edges_d3_filtered <-
edges_d3 %>%
filter(from %in% nodes_d3_min_freq$id | to %in% nodes_d3_filtered$id)
# filter the nodes list to contain only nodes that are in or are linked to
# nodes in the filtered edge list
nodes_d3_filtered <-
nodes_d3 %>%
filter(id %in% unlist(select(edges_d3_filtered, from, to)))
# re-adjust the from and to values to reflect the new positions of nodes in
# the filtered nodes list
edges_d3_filtered$from <- match(edges_d3_filtered$from, nodes_d3_filtered$id) - 1
edges_d3_filtered$to <- match(edges_d3_filtered$to, nodes_d3_filtered$id) - 1
forceNetwork(Links = edges_d3_filtered, Nodes = nodes_d3_filtered,
Source = "from", Target = "to", NodeID = "label",
Group = "id", Value = "value", opacity = 1, fontSize = 20,
zoom = TRUE, Nodesize = "peso", arrows = TRUE)
Trying to run a shiny app, but keep getting the error: Error in filter_impl: Result must have length 4090, not 0
I've tried:
debugging by removing individual filters to try isolate the issue.
using dplyr::filter to force dplr's filter
ensured all filters are in a reactive function
checked whether it was an issue of sharing inputs between ui.R and server.r
checked whether it is caused by a previous df transformation.
Spent about 3 hours trying to find the cause, with no success.
Can you please help?
Server.R
rm(list = ls())
library(shiny)
library(tidyverse)
library(shiny)
library(ggplot2)
library(singer)
library(ggvis)
library(dplyr)
# Set Up DataFrames
data(package = "singer")
data(singer_locations)
sdf <- singer_locations %>% filter(year != 0) # filter out songs with missing years for simplicity
sdf %>% skim() %>% kable() # Check to see missing and incomplete values
sdf <- sdf %>% filter(complete.cases(.)) # filter out songs with missing observations for simplicity
sdf %>% skim() %>% kable() # Check to see if missing and incomplete values have been ignored
sdf <- sdf %>% select(
track_id, title, song_id, release, artist_id, artist_name, year, duration,
artist_hotttnesss, artist_familiarity, name, city, longitude, latitude
)
# add new columns with rounded data (for nicer graphs later)
sdf$latitude_rounded <- round(sdf$latitude, 0)
sdf$longitude_rounded <- round(sdf$longitude, 0)
sdf$duration_rounded <- round(sdf$duration, 0)
# Add song_popularity & very_popular_song columns
pops <- sdf$artist_hotttnesss + sdf$artist_familiarity
sdf$artist_popularity <- round(pops, 0)
sdf$very_popular_song <- round(sdf$artist_popularity)
sdf$very_popular_song[sdf$very_popular_song < 1] <- "No"
sdf$very_popular_song[sdf$very_popular_song >= 1] <- "Yes"
# Select() relevant variables so they can be passed into server below (without having to use df[,"VAR"])
songs_list <- sdf %>% select(
track_id, title, song_id, release, artist_id, artist_name, year, duration_rounded, duration,
artist_hotttnesss, artist_familiarity, name, city, latitude_rounded, longitude_rounded, longitude,
latitude, artist_popularity, very_popular_song
)
#axis_variables <- reactive({
axis_variables <- c(
"Length of Song (Seconds)" = "duration_rounded",
"Rating" = "artist_hotttnesss",
"Rating" = "artist_familiarity",
"Year" = "year",
"Popularity Rating" = "artist_popularity"
)
################################### SHINY SERVER #########################################
function(input, output) {
songs <- reactive({ # Create Reactive Filtering Component
duration_s <- input$duration_s
artist_hotttnesss_s <- input$artist_hotttnesss_s
artist_familiarity_s <- input$artist_familiarity_s
latitude_s <- input$latitude_s
longitude_s <- input$longitude_s
year_s <- input$year_s
artist_popularity_s <- input$artist_popularity_s
# Apply filters
songs_df <- songs_list %>%
dplyr::filter(
duration_rounded >= duration_s,
artist_hotttnesss >= artist_hotttnesss_s,
artist_familiarity >= artist_familiarity_s,
latitude_rounded >= latitude_s,
longitude_rounded >= longitude_s,
year >= year_s,
artist_popularity >= artist_popularity_s
) %>%
arrange(duration_rounded)
# filter by city option
if (input$city_in != "All") {
city_in_temp <- paste0("%", input$city_in, "%")
songs_df <- songs_df %>% dplyr::filter(songs_df$city %like% city_in_temp)
}
# filter by artist_name option
if (input$artist_name_in != "" && !is.null(input$artist_name_in)) {
artist_name_temp <- paste0("%", input$artist_name_in, "%")
songs_df <- songs_df %>% dplyr::filter(songs_df$artist_name %like% artist_name_temp)
}
songs_df <- as.data.frame(songs_df)
songs_df # return df
})
# search fuction
song_search <- function(s) {
if (is.null(s)) return(NULL)
if (is.null(s$track_id)) return(NULL)
# Isolate the given ID
songs_df <- isolate(songs())
temp_song <- songs_df[songs_df$track_id == s$track_id, ]
paste0("<b>", temp_song$artist_name, "</b><br>",
temp_song$year, "<br>",
"popularity ", format(temp_song$artist_popularity, big.mark = ",", scientific = FALSE)
)
}
# A reactive expression with the ggvis plot
vis <- reactive({
# setting variablex & variabley (input names are type str)
variablex <- prop("x", as.symbol(input$variablex))
variabley <- prop("y", as.symbol(input$variabley))
# Lables for axes
xvar_name <- names(axis_variables)[axis_variables == input$variablex]
yvar_name <- names(axis_variables)[axis_variables == input$variabley]
songs %>%
ggvis(x = variablex, y = variabley) %>%
layer_points(size := 50, size.hover := 200,
fillOpacity := 0.2, fillOpacity.hover := 0.5,
stroke = ~artist_popularity, key := ~artist_name) %>%
add_tooltip(song_search, "hover") %>%
add_axis("x", title = xvar_name) %>%
add_axis("y", title = yvar_name) %>%
add_legend("stroke", title = "Very Popular", values = c("Yes", "No")) %>%
scale_nominal("stroke", domain = c("Yes", "No"),
range = c("orange", "#aaa")) %>%
set_options(width = 500, height = 500)
})
vis %>% bind_shiny("plot1")
output$songs_selected <- renderText({ nrow(songs()) })
}
Ui.R
rm(list = ls())
library(tidyverse)
library(shiny)
library(ggplot2)
library(singer)
library(ggvis)
library(dplyr)
#axis_variables <- reactive({
axis_variables <- c(
"Length of Song (Seconds)" = "duration_rounded",
"Hotness Rating" = "artist_hotttnesss",
"Familiarity Rating" = "artist_familiarity",
"Year" = "year",
"Popularity Rating" = "artist_popularity"
)
# Define UI for application that draws a histogram
shinyUI(fluidPage(
shinythemes::themeSelector(),
titlePanel("Artist & Song Data"),
fluidRow(
column(3,
wellPanel(
h4("Filter By"),
# Slider Options for Data Exploration
sliderInput("duration_s", "Minimum duration of song (seconds)", 10, 500, 100, step = 10),
sliderInput("year_s", "Year released", 1900, 2018, value = c(1980, 2018)),
sliderInput("artist_hotttnesss_s", "Ranking / 10 for popularity", 0, 2, 0, step = 0.1),
sliderInput("artist_familiarity_s", "Ranking / 10 for familiarity", 0, 2, 0, step = 0.1),
sliderInput("artist_popularity", "Ranking / 10 for familiarity", 0, 2, 0, step = 0.1),
# Filter by custom input condition
textInput("city_in", "Name of the city"),
textInput("artist_name_in", "Artist's name contains (e.g Pink f)")
),
wellPanel(
selectInput("variablex", "X-axis", axis_variables, selected = "year"),
selectInput("variabley", "Y-axis", axis_variables, selected = "duration_rounded")
)
),
column(9,
ggvisOutput("plot1"),
wellPanel(
span("Degrees of Freedom",
textOutput("songs_selected")
)
)
)
)
It looks like you are filtering using data created by input$XXX. Try to put req(input$XXX, req(input$YYY, ...) at the beginning of your reactive element(s).
Also read this tweet about starting with rm(list = ls()).
I have two data sets df_state and df_city.
df_state has a summed up of a numeric value(net_value_x).
df_city has a break down of the numeric value(value_x) at city level
This is just a sample for the sate Texas:
df_state:
state_abb, net_value_x
.
.
TX 18.94
.
.
df_city:
state_abb, city, value_x
.
.
TX Dallas 14
TX Houston 2
TX Austin 2.94
.
.
This is the code i used to plot at state level using hcmap function from highcharter.
hcmap("countries/us/us-all", data = df_state, value = "net_value_x",
joinBy = c("hc-a2", "code"), name = "net_value_x",
dataLabels = list(enabled = TRUE, format = "{point.name}"),
borderColor = "#FAFAFA", borderWidth = 0.1,
tooltip = list(valueDecimals = 2))
This is the map that i get. Hovering sample data for Texas.
What I need is to drill down this further into city level. Also i may get county level data in the future.
I am visualizing something like this in the link:
highchart drilldown
While the drilldown feature of highcharter is useful, when you have data in every state, it would require loading the map for each underlying state at render. An approach I have taken is to essentially switch between plotting the state map and a county map as follows.
## Library
library(shiny)
library(shinyjs)
library(dplyr)
library(highcharter)
library(stringr)
## Load maps at start for speed
maps <- sapply(
X = c("us-all", "us-tx-all"),
simplify = FALSE,
FUN = function(x) { highcharter::download_map_data(paste0("countries/us/", x)) }
)
ui <- shiny::fluidPage(
shinyjs::useShinyjs(),
highcharter::highchartOutput(outputId = "map"),
shiny::uiOutput(outputId = "ui")
)
server <- function(input, output, session) {
## USA map with just TX as example
state_map <- shiny::reactive({
highcharter::highchart() %>%
highcharter::hc_add_series_map(
map = maps[["us-all"]],
df = data.frame(state_abbr = c("TX"), y = c(10)),
joinBy = c("postal-code", "state_abbr"),
value = "y"
) %>%
highcharter::hc_plotOptions(
series = list(
allowPointSelect = TRUE,
events = list(
click = htmlwidgets::JS(
"function(event) {
Shiny.setInputValue(
'geo_click',
event.point.state_abbr,
{priority: 'event'}
);
}"
)
)
)
)
})
## County map
county_map <- shiny::reactive({
highcharter::highchart() %>%
highcharter::hc_add_series_map(
map = maps[[paste0("us-", stringr::str_to_lower(input$geo_click), "-all")]],
df = data.frame(city = c("Gray", "Leon", "Lamb", "Duval"), y = c(1, 4, 2, 3)),
joinBy = c("name", "city"),
value = "y"
)
})
## Set to state map at outset
output$map <- highcharter::renderHighchart({ state_map() })
## If state clicked, add button to go back to state map
output$ui <- shiny::renderUI({
if (!is.null(input$geo_click)) {
shiny::actionButton(
inputId = "geo_button",
label = "Return to USA Map"
)
}
})
## If button clicked, reset input, hide button, and go back to state map
shiny::observeEvent(
eventExpr = input$geo_button,
handlerExpr = {
output$map <- highcharter::renderHighchart({ state_map() })
shinyjs::hide(id = "geo_button")
}
)
## If state clicked, go to county map and show button
shiny::observeEvent(
eventExpr = input$geo_click,
handlerExpr = {
output$map <- highcharter::renderHighchart({ county_map() })
shinyjs::show(id = "geo_button")
}
)
}
shiny::shinyApp(ui = ui, server = server)
I am creating a Shiny dashboard with a dataframe of start longitude/latitude and end longitude/latitude cooridnated that I have plotted in R using the leaflet package:
`m=leaflet()%>%
addTiles() %>%
addMarkers(lng=(data$Start_long[i:j]), lat=(data$Start_lat[i:j]),popup="Start") %>%
addCircleMarkers(lng=(data$End_long[i:j]), lat=(data$End_lat[i:j]),popup="End",clusterOptions=markerClusterOptions())`
I was wondering if there was a way to join the start and end coordinated by public transport routes (maybe through google maps API or in-library functions or failing that, join the coordinates by a straight line?
You can use my googleway package to both get the directions/routes, and plot it on a Google map
To use Google's API you need a valid key for each API you want to use. In this case you'll want a directions key, and for plotting the map you'll want a maps javascript key
(You can generate one key and enable it for both APIs if you wish)
To call the Directions API and plot it in R, you can do
library(googleway)
api_key <- "your_directions_api_key"
map_key <- "your_maps_api_key"
## set up a data.frame of locations
## can also use 'lat/lon' coordinates as the origin/destination
df_locations <- data.frame(
origin = c("Melbourne, Australia", "Sydney, Australia")
, destination = c("Sydney, Australia", "Brisbane, Australia")
, stringsAsFactors = F
)
## loop over each pair of locations, and extract the polyline from the result
lst_directions <- apply(df_locations, 1, function(x){
res <- google_directions(
key = api_key
, origin = x[['origin']]
, destination = x[['destination']]
)
df_result <- data.frame(
origin = x[['origin']]
, destination = x[['destination']]
, route = res$routes$overview_polyline$points
)
return(df_result)
})
## convert the results to a data.frame
df_directions <- do.call(rbind, lst_directions)
## plot the map
google_map(key = map_key ) %>%
add_polylines(data = df_directions, polyline = "route")
And similarly in a Shiny app
library(shiny)
library(shinydashboard)
library(googleway)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
textInput(inputId = "origin", label = "Origin"),
textInput(inputId = "destination", label = "Destination"),
actionButton(inputId = "getRoute", label = "Get Rotue"),
google_mapOutput("myMap")
)
)
server <- function(input, output){
api_key <- "your_directions_api_key"
map_key <- "your_maps_api_key"
df_route <- eventReactive(input$getRoute,{
print("getting route")
o <- input$origin
d <- input$destination
return(data.frame(origin = o, destination = d, stringsAsFactors = F))
})
output$myMap <- renderGoogle_map({
df <- df_route()
print(df)
if(df$origin == "" | df$destination == "")
return()
res <- google_directions(
key = api_key
, origin = df$origin
, destination = df$destination
)
df_route <- data.frame(route = res$routes$overview_polyline$points)
google_map(key = map_key ) %>%
add_polylines(data = df_route, polyline = "route")
})
}
shinyApp(ui, server)
You can addPolylines() to the map.
It takes two vectors as arguments, one for the lat and one for the lng, where each row is a 'waypoint'.
It's difficult to help you without knowing the structure of your data.
MRE:
library(leaflet)
cities <- read.csv(textConnection("
City,Lat,Long,Pop
Boston,42.3601,-71.0589,645966
Hartford,41.7627,-72.6743,125017
New York City,40.7127,-74.0059,8406000
Philadelphia,39.9500,-75.1667,1553000
Pittsburgh,40.4397,-79.9764,305841
Providence,41.8236,-71.4222,177994
"))
leaflet() %>%
addTiles() %>%
addPolylines(lat = cities$Lat, lng = cities$Long)
I use "for loop" to solve such problem,just draw polylines one by one.
(sorry for my Chinese expression ^_^)
for examply :
for(i in 1:nrow(sz)){
if(i<=nrow(sz) ){
a <- as.numeric(c(sz[i,c(8,10)]));
b <- as.numeric(c(sz[i,c(9,11)]));
A <- A %>% addPolylines(a,b,group=NULL,weight = 1,color = "brown",
stroke = TRUE,fill = NULL,opacity = 0.8)}
or like a more complex one
for(j in 0:23){if(j<=23)
#j--切每小时数据
j1 <- as.character(paste(j,"点",sep=''))
sz <- sz121[sz121$h==j,]
sz_4 <- sz121[sz121$bi_state==4 &sz121$h==j ,]
sz_8 <- sz121[sz121$bi_state==8&sz121$h==j,]
#还原A
A <- leaflet(sz121) %>% amap() %>% addLabelOnlyMarkers(~s_lon,~s_lat) %>%
addLegend(title=j1,colors=NULL,labels =NULL,position="topleft")
A <- A %>%addCircleMarkers(data=sz_8,~s_lon,~s_lat,color="orange",fill=TRUE,fillColor = "red", opacity = 1,fillOpacity=0.8,
weight =1,radius = 10) %>%addCircleMarkers(data=sz_4,~s_lon,~s_lat,color="black",fill=TRUE,fillColor = "red",
opacity = 1,fillOpacity=0.8,weight =5,radius = 10 ) %>%
addCircleMarkers(data=sz_8,~e_lon,~e_lat,color="orange",fill=TRUE,fillColor = "blue", opacity = 1,fillOpacity=0.8,weight=1,radius = 10) %>%
addCircleMarkers(data=sz_4,~e_lon,~e_lat,color="black",fill=TRUE,fillColor = "blue", opacity = 1,fillOpacity=0.8,weight =5,radius = 10 )
for(i in 1:nrow(sz)){
#i--画路径
if(i<=nrow(sz) ){
a <- as.numeric(c(sz[i,c(8,10)]));
b <- as.numeric(c(sz[i,c(9,11)]));
A <- A %>% addPolylines(a,b,group=NULL,weight = 1,color = "brown",stroke = TRUE,fill = NULL,opacity = 0.8)
}
if(i==nrow(sz)){print(A)}
}
Sys.sleep(3)
}
I am trying to make the colors in a ggvis plot remain consistent whenever the data is re-plotted based on the factors (unfortunately I apparently lack enough reputation to include pictures to show you).
I could only find one other post about this controlling-color-of-factor-group-in-ggvis-r but none of his solutions or workarounds work in my situation.
my data looks like this:
month year date entity_name prefix module module_entry_key entity_table_name count
0 January 2011 2011.000 AbLibrary LIB Base BS AB_LIBRARY 0
1 February 2011 2011.083 AbLibrary LIB Base BS AB_LIBRARY 0
2 March 2011 2011.167 AbLibrary LIB Base BS AB_LIBRARY 0
3 April 2011 2011.250 AbLibrary LIB Base BS AB_LIBRARY 0
4 May 2011 2011.333 AbLibrary LIB Base BS AB_LIBRARY 0
5 June 2011 2011.417 AbLibrary LIB Base BS AB_LIBRARY 0
3000 January 2011 2011.000 Vector VEC Base BS VECTOR 0
3001 February 2011 2011.083 Vector VEC Base BS VECTOR 0
3002 March 2011 2011.167 Vector VEC Base BS VECTOR 0
3003 April 2011 2011.250 Vector VEC Base BS VECTOR 569
3004 May 2011 2011.333 Vector VEC Base BS VECTOR 664
3005 June 2011 2011.417 Vector VEC Base BS VECTOR 775
I'm using a shiny app to display the page in a browser, and the relevant code is:
# render the plot, filtering for entities within the module minus any entities selected from the exclude panel
plot <- reactive({
if (input$filter==1){
data <- dplyr::filter(.data=melted, module_entry_key %in% input$module)
}
else{
data <- dplyr::filter(.data=melted, entity_name == input$entity)
}
data <- dplyr::filter(.data=data, !entity_name %in% input$excluded)
data$entity_name <- factor(data$entity_name)
data %>%
ggvis(x = ~date, y = ~count, fill = ~entity_name, key := ~id, fillOpacity := 0.5, fillOpacity.hover := 0.9) %>%
add_legend("fill", title="Entities") %>%
layer_points() %>%
add_tooltip(tooltipText, "hover") %>%
add_axis("y", title = "Count", title_offset = 50) %>%
add_axis("x", title="Date", title_offset=50, subdivide=6, tick_size_minor=3, format=parseDate(~year, ~month))
})
the filter is creating the subset of "melted" as "data" based on the filters in the UI (see picture)
since as far as I can tell there is no way to associate a fill color to a factor (the entity name) explicitly and the color is chosen by alphabetical order of the factors, whenever I make a new subset of data the colors are changed.
Is there any way to work around this?
(full shiny code)
server.R
library(ggvis)
library(shiny)
library(dplyr)
shinyServer(function(input, output, session){
modules_list <- as.character(c("Base" = "BS",
"Screening" = "SC",
"Protein Engineering" = "EN",
"Protein Production" = "PP",
"CD",
"PT",
"PD"))
#melted <- read.table(file="~/dataOut.txt", sep="\t", strip.white=TRUE, row.names=1, header=TRUE);
modules <- as.character(as.vector(unique(melted$module_entry_key)))
modules <- modules[modules != "null"]
entities <- as.character(as.vector(unique(melted$entity_name)))
entities <- entities[entities != "null"]
for (i in entities){
melted <- rbind(melted, data.frame(month=NA, year=NA, date=NA, entity_name=i, prefix=NA, module=NA, module_entry_key=NA, entity_table_name=NA, count=NA))
}
melted$id <- 1:nrow(melted)
#create ui checkbox for modules in the data
output$module_list <- renderUI({
checkboxGroupInput(inputId = "module",
label = "Module",
choices = modules,
selected = "BS")
})
#create the ui list for entities
output$entity_list <- renderUI({
checkboxGroupInput(
inputId = "entity",
label = "Entity",
choices = entities,
selected = "Vector"
)
})
#ex <- entities
#create the checkboxGroupInput with entities to 'exclude'
output$exclusion_entities <- renderUI({
checkboxGroupInput(inputId = "excluded", label = "Exclude",
choices = entities)
})
#update the excluded entities list with entities within a particular module
observe({
if (input$filter==1)
ex1 <- as.character(as.vector(unique(dplyr::filter(.data=melted, module_entry_key %in% input$module)$entity_name)))
updateCheckboxGroupInput(session, inputId = "excluded", "Exclude", choices=ex1, selected = input$excluded )
})
# render the plot, filtering for entities within the module minus any entities selected from the exclude panel
plot <- reactive({
if (input$filter==1){
data <- dplyr::filter(.data=melted, module_entry_key %in% input$module)
}
else{
data <- dplyr::filter(.data=melted, entity_name == input$entity)
}
data <- dplyr::filter(.data=data, !entity_name %in% input$excluded)
data$entity_name <- factor(data$entity_name)
data %>%
ggvis(x = ~date, y = ~count, fill = ~entity_name, key := ~id, fillOpacity := 0.5, fillOpacity.hover := 0.9) %>%
add_legend("fill", title="Entities") %>%
layer_points() %>%
add_tooltip(tooltipText, "hover") %>%
add_axis("y", title = "Count", title_offset = 50) %>%
add_axis("x", title="Date", title_offset=50, subdivide=6, tick_size_minor=3, format=parseDate(~year, ~month))
})
#function to add color and mouse-over effect to layer_points() (unused in this code)
points <- reactive({
layer_points(fillOpacity := 0.5, fillOpacity.hover := 1, fill.hover := "red")
})
#d3 date format for formatting x-axis text
parseDate <- function(year, month){
paste("d3.time.format(\"%Y\").parse(", year, ")", sep="")
}
#function for what to display in mouse-hover tooltip
tooltipText <- function(x) {
if(is.null(x)) return(NULL)
row <- melted[melted$id == x$id, ]
paste(row$entity_name, ": ", row$count, sep="")
}
#bind the plot to the UI
plot %>% #layer_points(fill = ~factor(entity_name)) %>%
bind_shiny("ggvis")
#select all button for modules
observe({
if (input$selectall ==0){
return(NULL)
}
else if ((input$selectall%%2)==0){
updateCheckboxGroupInput(session, inputId = "module", "Module", choices = modules)
}
else{
updateCheckboxGroupInput(session, inputId = "module", "Module", choices=modules, selected=modules)
}
})
#select all button for excluded entities
observe({
list <- as.character(as.vector(unique(dplyr::filter(.data=melted, module_entry_key %in% input$module)$entity_name)))
if (input$exclude_all ==0){
return(NULL)
}
else if ((input$exclude_all%%2)==0){
updateCheckboxGroupInput(session, inputId = "excluded", "Exclude", choices=list )
}
else{
updateCheckboxGroupInput(session, inputId = "excluded", "Exclude", choices=list, selected=list )
}
})
#---general output / debugging stuff ----#
output$table <- renderTable({dataInput()})
output$entity_selected = renderPrint({
list <- as.character(as.vector(unique(dplyr::filter(.data=melted, module_entry_key %in% input$module)$entity_name)))
entities[!entities %in% input$excluded & entities %in% list]
})
output$filter_value = renderPrint({input$filter})
output$modules = renderPrint({input$module})
output$link = renderPrint(input$selectall%%2)
#----------------------------------------#
})
ui.R
library(shiny)
shinyUI(fluidPage(
titlePanel("DB Analysis"),
sidebarLayout(
sidebarPanel(
width=3,
radioButtons(inputId="filter",
label="Filter",
choices = list("By Module" = 1, "By Entity" = 2),
selected = 1),
conditionalPanel(condition = "input.filter == 1",
uiOutput("module_list"),
actionButton("selectall", "Select All"),
uiOutput("exclusion_entities"),
actionButton("exclude_all", "Select All")
),
conditionalPanel(condition = "input.filter == 2",
uiOutput("entity_list")
)
),
mainPanel(
h2("Cumulative Entity Counts over Time (years)", align="center"),
#verbatimTextOutput("value"),
#verbatimTextOutput("filter_value"),
#verbatimTextOutput("modules"),
#tableOutput("table"),
ggvisOutput("ggvis"),
verbatimTextOutput("link"),
verbatimTextOutput("entity_selected")
#textOutput("entities_plot")
)
)
)
)
This is probably the best way to do it. Try something like this:
df[which(df$entity_name == "AbLibrary"),]$color <- "FF0000"
df[which(df$entity_name == "Vector"),]$color <- "#FFB90F"
For each one in your data frame. Set your fill then to color each time. The only problem is trying to make a legend. (I have been trying to figure that out, so if I find it I will edit this post.