一、參考資料

  1. 電子書:Mastering Shiny (Hadley Wickham著作)
  2. 網頁介紹:R 講題分享 – 利用 R 和 Shiny 製作網頁應用 (作者:Taiwan R User Group)
  3. Shiny作品集:Shiny Gallery
  4. Rstudio官網教學
  5. R Shiny cheat sheet: [網站頁面] [直接下載]

二、Shiny簡介

1. Shiny範例介紹

以R Shiny最基本範例來做介紹,在R程式中輸入:

library(shiny)
runExample("01_hello")

執行後會跳出Shiny範例:

以下為範例檔案的程式碼:

library(shiny)

# Define UI for app that draws a histogram ----
ui <- fluidPage(

  # App title ----
  titlePanel("Hello Shiny!"),

  # Sidebar layout with input and output definitions ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      # Input: Slider for the number of bins ----
      sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)

    ),

    # Main panel for displaying outputs ----
    mainPanel(

      # Output: Histogram ----
      plotOutput(outputId = "distPlot")

    )
  )
)

# Define server logic required to draw a histogram ----
server <- function(input, output) {

  # Histogram of the Old Faithful Geyser Data ----
  # with requested number of bins
  # This expression that generates a histogram is wrapped in a call
  # to renderPlot to indicate that:
  #
  # 1. It is "reactive" and therefore should be automatically
  #    re-executed when inputs (input$bins) change
  # 2. Its output type is a plot
  output$distPlot <- renderPlot({

    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = input$bins + 1)

    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")

    })

}

# Create Shiny app ----
shinyApp(ui = ui, server = server)
  • faithful資料集說明
?faithful

Waiting time between eruptions and the duration of the eruption for the Old Faithful geyser in Yellowstone National Park, Wyoming, USA.

A data frame with 272 observations on 2 variables.

欄位名稱 資料類型 說明
eruptions(噴發時間長度) numeric Eruption time in mins
waiting(下一次噴發等待時間) numeric Waiting time to next eruption (in mins)

圖片來源: https://en.wikipedia.org/wiki/Old_Faithful

2. 小試身手

請繪製噴發時間長度(eruptions)的直方圖在Shiny上。

3. 呈現資料表

R shiny有提供呈現資料表的函數renderDataTable,但呈現表格的方式會不太好看,所以通常會使用DT套件,此套件說明可以參考此頁面

接下來在剛剛的範例集中,呈現faithful的資料表給使用者觀看。

library(shiny)
library(DT)

# UI
ui <- fluidPage(
  
  HTML('<center>'),
  h3("Faithful資料表-DT版"),
  HTML('</center>'),
  dataTableOutput("DT_table"),
  
  hr(),
  
  HTML('<center>'),
  h3("Faithful資料表-Shiny版"),
  HTML('</center>'),
  tableOutput("shiny_table")
)

# Server
server <- function(input, output) {

  # DT版
  output$DT_table <- renderDataTable({
    datatable(faithful)
  })
  
  # Shiny版
  output$shiny_table <- renderTable({
    faithful
  })
}

# Create Shiny app
shinyApp(ui = ui, server = server)

三、Shiny版面介紹

Shiny版面說明可參考Hadley Wickham的Mastering Shiny書籍。

以下介紹我自己常用的版型:

library(shiny)
library(shinythemes)

# UI
ui <- navbarPage("Shiny範例", id = "tabs",
                 
                 # 主題設定
                 theme = shinytheme("flatly"),
                 
                 #################### 主題1 ####################
                 tabPanel("主題1", value = "主題1",
                          
                          tabsetPanel(type = "tabs",
                                      
                                      tabPanel("頁籤1", value = "頁籤1",
                                               
                                               h3("這裡是頁籤1頁面")
                                      ),
                                      
                                      tabPanel("頁籤2", value = "頁籤2",
                                               h3("這裡是頁籤2頁面")
                                      )
                          )
                 ),
                 
                 #################### 主題2 ####################
                 tabPanel("主題2", value = "主題2",
                          
                          h3("這裡是主題2頁面"),
                          
                          sidebarLayout(
                            
                            sidebarPanel(
                              
                              h3("這裡是sidebarPanel")
                            ),
                            
                            mainPanel(
                              
                              h3("這裡是mainPanel")
                            )
                          )
                 ),
                 
                 
                 #################### 加入CSS修改字體 ####################
                 tags$head(tags$style(HTML("@import url('//fonts.googleapis.com/css?family=Noto+Sans+TC');
                                   body, h2, h3, center {font-family: 'Noto Sans TC', sans-serif;}")))
)

# Server
server <- function(input, output) {
  
}

# Create Shiny app
shinyApp(ui = ui, server = server)
  • 補充: Shiny的主題設定可到此頁面來挑選。

四、Shiny Widgets介紹

Shiny範例中有滑桿(Slider)可以選擇直方圖要畫的bin時外,Shiny也有提供其他常見的小工具,可以參考Shiny Widgets Gallery

接下我們以Shiny範例來實作不同的Shiny Widgets功能。

1. 以選擇框(Select box)來調整bins

此範例是呈現如何透過selectInput來讓使用者挑選bins。

library(shiny)

# UI
ui <- fluidPage(
  
  # 選擇框
  selectInput("bins", 
              label = h3("Please select bins:"), 
              choices = list("15" = 15, 
                             "20" = 20, 
                             "30" = 30), 
              selected = 15),
  
  # 呈現直方圖
  plotOutput(outputId = "distPlot")
)

# Server
server <- function(input, output) {
  
  output$distPlot <- renderPlot({
    
    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = as.numeric(input$bins) + 1)
    
    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")
    
  })
}

# Create Shiny app
shinyApp(ui = ui, server = server)

2. 從後台控制選項內容

從上面的範例可以看到我們是在前端設定好可以調整的選項,但有時候我們希望選項能夠依據資料狀況自動呈現,例如說根據資料樣本數來決定bins可以選擇的大小。此時可以在server端透過updateSelectInput函數來更新ui的選擇項目。

library(shiny)

# UI
ui <- fluidPage(
  
  # 選擇框
  selectInput("bins", 
              label = h3("Please select bins:"), 
              choices = NULL, 
              selected = NULL),
  
  # 呈現直方圖
  plotOutput(outputId = "distPlot")
)

# Server
server <- function(input, output, session) {
  
  # 更新選項內容
  binsOption <- seq(5, 30, 5)
  binsOption <- split(unname(binsOption), binsOption)
  updateSelectInput(session, "bins", 
                    choices = binsOption,
                    selected = binsOption[[1]])
  
  output$distPlot <- renderPlot({
    
    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = as.numeric(input$bins) + 1)
    
    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")
    
  })
}

# Create Shiny app
shinyApp(ui = ui, server = server)

3. 以輸入值形式(Text input)來調整bins

此範例是呈現如何透過textInput讓使用者自行輸入bins。

library(shiny)

# UI
ui <- fluidPage(
  
  # 輸入對話框
  textInput("bins", label = h3("請輸入bins:"), value = "5"),
  
  # 呈現直方圖
  plotOutput(outputId = "distPlot")
)

# Server
server <- function(input, output, session) {
  
  output$distPlot <- renderPlot({
    
    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = as.numeric(input$bins) + 1)
    
    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")
    
  })
}

# Create Shiny app
shinyApp(ui = ui, server = server)

五、觸發事件寫法

以輸入值形式(Text input)來調整bins範例中,我們透過textInput讓使用者自行輸入bins來決定繪製直方圖,但可能會發生兩個問題:

  1. 使用者輸入不合適的值導致圖形無法繪製
  2. 使用者還沒輸入完馬上跑出圖形

為解決上述問題,我們可以設定一個按鈕,當使用者輸入完想要呈現的值後,按下按鈕圖才會繪製。此處需要加入actionButton函數做按鈕及observeEvent搭配isolate做觸發事件。

library(shiny)

# UI
ui <- fluidPage(
  
  # 輸入對話框
  textInput("bins", label = h3("請輸入bins:"), value = "5"),
  
  # 建立繪製按鈕
  actionButton("action", label = "繪製"),
  
  # 呈現直方圖
  plotOutput(outputId = "distPlot")
)

# Server
server <- function(input, output, session) {
  
  observeEvent(input$action, {
    
    output$distPlot <- renderPlot({
      
      binNums <- as.numeric(isolate(input$bins))
      x    <- faithful$waiting
      bins <- seq(min(x), max(x), length.out = binNums + 1)
      
      hist(x, breaks = bins, col = "#75AADB", border = "white",
           xlab = "Waiting time to next eruption (in mins)",
           main = "Histogram of waiting times")
      
    })
  })
}

# Create Shiny app
shinyApp(ui = ui, server = server)

若使用者輸入錯誤的值,則提示輸入錯誤之寫法:

library(shiny)

# UI
ui <- fluidPage(
  
  # 輸入對話框
  textInput("bins", label = h3("請輸入bins(需介於5至30之間):"), value = "5"),
  
  # 建立繪製按鈕
  actionButton("action", label = "繪製"),
  
  # 輸出警告
  span(textOutput(outputId = "warningText"), style = "color:red; font-size:20px"),
  
  # 呈現直方圖
  plotOutput(outputId = "distPlot")
)

# Server
server <- function(input, output, session) {
  
  observeEvent(input$action, {
    
    # 讀取使用者輸入的值
    binNums <- as.numeric(isolate(input$bins))
    binNums <- ifelse(is.na(binNums), 0, binNums)
    
    # 判斷是否在合理範圍
    if((binNums >= 5) & (binNums <= 30)){
      
      # 位在合理範圍內清空警告提醒並繪圖 
      output$warningText <- renderText({NULL})
      output$distPlot <- renderPlot({
        
        x    <- faithful$waiting
        bins <- seq(min(x), max(x), length.out = binNums + 1)
        
        hist(x, breaks = bins, col = "#75AADB", border = "white",
             xlab = "Waiting time to next eruption (in mins)",
             main = "Histogram of waiting times")
      })
      
    }else{
      
      # 沒有在合理範圍內則輸出警告提醒及清空圖形
      output$warningText <- renderText({"請輸入正確的值域範圍唷!"})
      output$distPlot <- renderPlot({NULL})
    }
  })
}

# Create Shiny app
shinyApp(ui = ui, server = server)

六、上傳Shiny

做好的Shiny如果想讓別人能夠觀看使用(即對外服務),可以上傳到R Studio的Shinyapps空間上或者自己架設R Shiny Server。

1. R Shinyapps介紹

可參考下列頁面資訊:

  1. Shinyapps.io官網
  2. shinyapps.io教學

在Shinyapps.io官網登入後,即可看到官網說明上傳步驟:

上述步驟接在R程式執行,步驟1及步驟2只要執行1次即可,要特別注意步驟2的token和secret資訊不能輕易給別人,否則他就可以操控你的帳戶。

步驟3是上傳Shiny的語法,將想要上傳的Shiny程式碼放在一個資料夾內,資料夾名稱即為專案名稱。Shiny程式碼的檔名請命名為app.r,一定要命名為這個,這樣才知道哪個是Shiny要執行的檔案。另外程式碼一定要是utf8編碼,否則上傳會出錯。

執行後即可在看到專案,點選網址即可看到剛製作的Shiny作品,其他人也可以透過此網址來觀賞你的Shiny作品。

如果想要刪除專案,先點選紅框左邊的盒子圖案(封存),再按下右邊的垃圾桶圖案,即可刪除專案。

2. R Shiny Server

由於Shinyapps.io對於部分套件並不支援,所以如果想要使用其他套件的話,可以自己架設R Shiny Server來對外服務。

R Shiny Server架設只能夠在linux作業系統上,安裝流程可參考Shiny官網說明

安裝完後記得要開啟防火牆3838的port(Shiny Server默認),才能讓提供服務。

Shiny程式放在/srv/shiny-server資料夾路徑底下即可對外服務。


七、實作-股票技術分析圖形繪製網站

目標: 使用者輸入股票代碼後,呈現股票技術分析圖形給使用者觀看。

library(shiny)
library(lubridate)
library(DT)
library(shinythemes)
library(tidyquant)
library(tidyverse)

# UI
ui <- navbarPage("Shiny", id = "tabs", 
                 
                 theme = shinytheme("flatly"),
                 
                 tabPanel("繪製技術分析圖形", value = "繪製技術分析圖形",
                          
                          sidebarLayout(
                            
                            # 側邊頁面
                            sidebarPanel(
                              
                              # 輸入股票代碼
                              textInput("stockCode", label = h5("請輸入Yahoo Finance股票代碼:"), 
                                        value = "", placeholder = ""),
                              
                              # 查詢日期範圍
                              dateRangeInput("dates", label = h5("請選擇繪製的日期範圍"),
                                             start = Sys.Date()-days(90),
                                             end = Sys.Date(),
                                             min = Sys.Date()-years(5),
                                             max = Sys.Date(),
                                             separator = "至"),
                              
                              # 查詢按鈕
                              actionButton("action", label = "查詢"),
                              
                              # 輸出警告
                              br(),
                              span(textOutput(outputId = "warningText"), style = "color:red"),
                              
                              # 說明資料來源
                              br(),
                              tags$div(
                                "本網站數據來源: ",
                                tags$a(href = "https://finance.yahoo.com/", "Yahoo Finance")
                              ),
                            ),
                            
                            # 主頁面
                            mainPanel(
                              
                              HTML("<center>"),
                              h3("技術分析圖形"),
                              HTML("</center>"),
                              plotOutput("stockPlot"),
                              hr(),
                              HTML("<center>"),
                              h3("股價資訊表"),
                              HTML("</center>"),
                              dataTableOutput("stockData")
                            )
                          )
                 ),
                 
                 # 字體設定:使用CSS語法
                 tags$head(
                   tags$style(HTML("@import url('//fonts.googleapis.com/css?family=Noto+Sans+TC');
                                   body, h2, h3, h4, h5, center {font-family: 'Noto Sans TC', sans-serif;}")))
)

# Server
server <- function(input, output, session) {
  
  # 啟動查詢
  observeEvent(input$action, {
    
    # 讀取使用者設定資訊
    stockCode <- isolate(input$stockCode)
    print(stockCode)
    stockCode <- ifelse(stockCode == "", "NA", stockCode)  # 防呆機制
    fromDate <- isolate(input$dates[1])
    toDate <- isolate(input$dates[2])
    
    # 下載股票資料
    stockPriceData <- tq_get(stockCode, 
                             get = "stock.price", 
                             from = fromDate, 
                             to = toDate)
    
    # 判斷是否有下載到資料
    if(is.na(stockPriceData)){
      
      # 若未載到資料則提示警告訊息給使用者
      output$warningText <- renderText({"請輸入正確的Yahoo Finance股票代碼唷!"})
      
    }else{
      
      # 清空警告訊息
      output$warningText <- renderText({NULL})
      
      # 繪製技術分析圖形
      output$stockPlot <- renderPlot({
        
        stockPriceData <- stockPriceData %>%
          mutate(date = ymd(date),
                 chg = ifelse(close > open, "up", "dn"),  # 判斷當日上漲/下跌
                 flat_bar = (open == close),              # 判斷當日開盤價是否等於收盤價(繪圖需額外繪製)
                 num = c(1:n()))                          # 樣本點編碼
        
        plotCode <- unique(stockPriceData$symbol)
        plotStartDate <- min(stockPriceData$date)
        plotEndDate <- max(stockPriceData$date)
        
        p <- ggplot(stockPriceData, aes(x = num)) +                                   # 建立畫布
          geom_linerange(aes(ymin = low, ymax = high)) +                              # 繪製上影線和下影線
          geom_rect(aes(xmin = num-1/3 , xmax =num+1/3, ymin = pmin(open, close),     # 繪製K棒實體部分
                        ymax = pmax(open, close), fill = chg)) +
          scale_fill_manual(values = c("dn" = "forestgreen", "up" = "firebrick")) +   # 改變K棒顏色,跌為綠色,漲為紅色
          scale_color_manual(values=c("blue", "orange", "green")) +                   # 改變移動平均線顏色(此處參考奇摩股市網的顏色)
          theme_bw() +                                                                # 將畫布底圖顏色改為白色
          labs(title = paste0(plotCode),                                              # 加入標題及改變X和Y軸名稱
               subtitle = paste0("繪製期間: ", plotStartDate, " 至 ", plotEndDate),
               x="", y="") +
          theme(text = element_text(size = 16),
                plot.title = element_text(hjust = 0.5),                               # 將標題置中
                plot.subtitle = element_text(hjust = 0.5),
                plot.margin = margin(0.5, 1.3, 0, 0, "cm"),
                legend.position="bottom",                                             # 把legend移到上面
                legend.title=element_blank()) +                                       # 將legend的title改掉
          guides(fill = FALSE)                                                        # 將fill方式繪圖的legend拿掉
        
        
        # 繪製垂直線位置 表示比對範圍
        dataRowNum <- nrow(stockPriceData)
        p <- p +
          scale_x_continuous(breaks = seq(dataRowNum, 1,-ceiling(dataRowNum/5)),             # 將x軸的標籤名稱更改為日期
                             labels = stockPriceData$date[seq(dataRowNum, 1,-ceiling(dataRowNum/5))], expand = c(0, 0))
        
        # 若有開盤價=收盤價之交易日資料,因geom_rect無法繪製,要用geom_segment補繪
        if(any(stockPriceData$flat_bar)){
          p <- p +
            geom_segment(data = stockPriceData[stockPriceData$flat_bar,],
                         aes(x = num-1/3, y = close, yend = close, xend = num+1/3))
        }
        
        return(p)
      })
      
      # 輸出表格
      output$stockData <- DT::renderDataTable({
        datatable(stockPriceData %>% arrange(desc(date)))
      })
    }
  })
}

# Run the application 
shinyApp(ui = ui, server = server)
  • 補充:plotly套件有股票技術分析圖形範例程式碼candlestick,可替換程式碼內的ggplot2寫法。

八、建立Shiny帳密登入機制

我們可透過shinymanager套件來為Shiny建立帳密登入機制,但需注意此套件無法在Shinyapps.io上使用。

以下程式碼摘自套件Github範例:

# 建立帳密資訊
credentials <- data.frame(
  user = c("shiny", "shinymanager"), # mandatory
  password = c("azerty", "12345"),   # mandatory
  start = c("2019-04-15"),           # optinal (all others)
  expire = c(NA, "2019-12-31"),
  admin = c(FALSE, TRUE),
  comment = "Simple and secure authentification mechanism 
  for single ‘Shiny’ applications.",
  stringsAsFactors = FALSE
)

library(shiny)
library(shinymanager)

# UI
ui <- fluidPage(
  tags$h2("My secure application"),
  verbatimTextOutput("auth_output")
)

# Wrap your UI with secure_app
ui <- secure_app(ui)

# Server
server <- function(input, output, session) {
  
  # call the server part
  # check_credentials returns a function to authenticate users
  res_auth <- secure_server(
    check_credentials = check_credentials(credentials)
  )
  
  output$auth_output <- renderPrint({
    reactiveValuesToList(res_auth)
  })
  
  # your classic server logic
  
}

shinyApp(ui, server)

補充

補充範例1

library(shiny)

# Define UI for app that draws a histogram ----
ui <- fluidPage(
  
  # App title ----
  titlePanel("Hello Shiny!"),
  
  # Sidebar layout with input and output definitions ----
  sidebarLayout(
    
    # Sidebar panel for inputs ----
    sidebarPanel(
      
      # Input: Slider for the number of bins ----
      sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)
    ),
    
    # Main panel for displaying outputs ----
    mainPanel(
      
      # Output: Histogram ----
      plotOutput(outputId = "waiting_distPlot"),
      plotOutput(outputId = "eruptions_distPlot")
    )
  )
)

# Define server logic required to draw a histogram ----
server <- function(input, output) {
  
  # Histogram of the Old Faithful Geyser Data ----
  # with requested number of bins
  # This expression that generates a histogram is wrapped in a call
  # to renderPlot to indicate that:
  #
  # 1. It is "reactive" and therefore should be automatically
  #    re-executed when inputs (input$bins) change
  # 2. Its output type is a plot
  
  output$waiting_distPlot <- renderPlot({
    
    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    
    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")
    
  })
  
  output$eruptions_distPlot <- renderPlot({
    
    x    <- faithful$eruptions
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    
    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")
    
  })
  
}

# Create Shiny app ----
shinyApp(ui = ui, server = server)

補充範例2

library(shiny)
library(lubridate)
library(DT)
library(shinythemes)
library(plotly)
library(tidyquant)
library(tidyverse)
library(shinymanager)


credentials <- data.frame(
  user = c("nsysu", "shiny", "shinymanager"), # mandatory
  password = c("nsysu", "azerty", "12345"),   # mandatory
  start = c("2019-04-15"),           # optinal (all others)
  expire = c(NA, NA, "2020-12-31"),
  admin = c(FALSE, FALSE, TRUE),
  comment = "Simple and secure authentification mechanism 
  for single ‘Shiny’ applications.",
  stringsAsFactors = FALSE
)


# UI
ui <- navbarPage("Shiny", id = "tabs", 
                 
                 theme = shinytheme("flatly"),
                 
                 tabPanel("繪製技術分析圖形", value = "繪製技術分析圖形",
                          
                          sidebarLayout(
                            
                            # 側邊頁面
                            sidebarPanel(
                              
                              # 輸入股票代碼
                              textInput("stockCode", label = h5("請輸入Yahoo Finance股票代碼:"), 
                                        value = "", placeholder = ""),
                              
                              # 查詢日期範圍
                              dateRangeInput("dates", label = h5("請選擇繪製的日期範圍"),
                                             start = Sys.Date()-days(90),
                                             end = Sys.Date(),
                                             min = Sys.Date()-years(5),
                                             max = Sys.Date(),
                                             separator = "至"),
                              
                              # 查詢按鈕
                              actionButton("action", label = "查詢"),
                              
                              # 輸出警告
                              br(),
                              span(textOutput(outputId = "warningText"), style = "color:red"),
                              
                              # 說明資料來源
                              br(),
                              tags$div(
                                "本網站數據來源: ",
                                tags$a(href = "https://finance.yahoo.com/", "Yahoo Finance")
                              ),
                            ),
                            
                            # 主頁面
                            mainPanel(
                              
                              HTML("<center>"),
                              h3("技術分析圖形"),
                              HTML("</center>"),
                              plotlyOutput("stockPlot"),
                              hr(),
                              HTML("<center>"),
                              h3("股價資訊表"),
                              HTML("</center>"),
                              dataTableOutput("stockData")
                            )
                          )
                 ),
                 
                 # 字體設定:使用CSS語法
                 tags$head(
                   tags$style(HTML("@import url('//fonts.googleapis.com/css?family=Noto+Sans+TC');
                                   body, h2, h3, h4, h5, center {font-family: 'Noto Sans TC', sans-serif;}")))
)

ui <- secure_app(ui, theme = shinytheme("flatly"))

# Server
server <- function(input, output, session) {
  
  # call the server part
  # check_credentials returns a function to authenticate users
  res_auth <- secure_server(
    check_credentials = check_credentials(credentials)
  )
  
  output$auth_output <- renderPrint({
    reactiveValuesToList(res_auth)
  })
  
  # 啟動查詢
  observeEvent(input$action, {
    
    # 讀取使用者設定資訊
    stockCode <- isolate(input$stockCode)
    print(stockCode)
    stockCode <- ifelse(stockCode == "", "NA", stockCode)  # 防呆機制
    fromDate <- isolate(input$dates[1])
    toDate <- isolate(input$dates[2])
    
    # 下載股票資料
    stockPriceData <- tq_get(stockCode, 
                             get = "stock.price", 
                             from = fromDate, 
                             to = toDate)
    
    # 判斷是否有下載到資料
    if(is.na(stockPriceData)){
      
      # 若未載到資料則提示警告訊息給使用者
      output$warningText <- renderText({"請輸入正確的Yahoo Finance股票代碼唷!"})
      
    }else{
      
      # 清空警告訊息
      output$warningText <- renderText({NULL})
      
      output$stockPlot <- renderPlotly({
        fig <- stockPriceData %>% plot_ly(x = ~date, type="candlestick",
                              open = ~open, close = ~close,
                              high = ~high, low = ~low) 
        fig <- fig %>% layout(title = "Basic Candlestick Chart")
      })
      
      
      # # 繪製技術分析圖形
      # output$stockPlot <- renderPlot({
      #   
      #   stockPriceData <- stockPriceData %>%
      #     mutate(date = ymd(date),
      #            chg = ifelse(close > open, "up", "dn"),  # 判斷當日上漲/下跌
      #            flat_bar = (open == close),              # 判斷當日開盤價是否等於收盤價(繪圖需額外繪製)
      #            num = c(1:n()))                          # 樣本點編碼
      #   
      #   plotCode <- unique(stockPriceData$symbol)
      #   plotStartDate <- min(stockPriceData$date)
      #   plotEndDate <- max(stockPriceData$date)
      #   
      #   p <- ggplot(stockPriceData, aes(x = num)) +                                   # 建立畫布
      #     geom_linerange(aes(ymin = low, ymax = high)) +                              # 繪製上影線和下影線
      #     geom_rect(aes(xmin = num-1/3 , xmax =num+1/3, ymin = pmin(open, close),     # 繪製K棒實體部分
      #                   ymax = pmax(open, close), fill = chg)) +
      #     scale_fill_manual(values = c("dn" = "forestgreen", "up" = "firebrick")) +   # 改變K棒顏色,跌為綠色,漲為紅色
      #     scale_color_manual(values=c("blue", "orange", "green")) +                   # 改變移動平均線顏色(此處參考奇摩股市網的顏色)
      #     theme_bw() +                                                                # 將畫布底圖顏色改為白色
      #     labs(title = paste0(plotCode),                                              # 加入標題及改變X和Y軸名稱
      #          subtitle = paste0("繪製期間: ", plotStartDate, " 至 ", plotEndDate),
      #          x="", y="") +
      #     theme(text = element_text(size = 16),
      #           plot.title = element_text(hjust = 0.5),                               # 將標題置中
      #           plot.subtitle = element_text(hjust = 0.5),
      #           plot.margin = margin(0.5, 1.3, 0, 0, "cm"),
      #           legend.position="bottom",                                             # 把legend移到上面
      #           legend.title=element_blank()) +                                       # 將legend的title改掉
      #     guides(fill = FALSE)                                                        # 將fill方式繪圖的legend拿掉
      #   
      #   
      #   # 繪製垂直線位置 表示比對範圍
      #   dataRowNum <- nrow(stockPriceData)
      #   p <- p +
      #     scale_x_continuous(breaks = seq(dataRowNum, 1,-ceiling(dataRowNum/5)),             # 將x軸的標籤名稱更改為日期
      #                        labels = stockPriceData$date[seq(dataRowNum, 1,-ceiling(dataRowNum/5))], expand = c(0, 0))
      #   
      #   # 若有開盤價=收盤價之交易日資料,因geom_rect無法繪製,要用geom_segment補繪
      #   if(any(stockPriceData$flat_bar)){
      #     p <- p +
      #       geom_segment(data = stockPriceData[stockPriceData$flat_bar,],
      #                    aes(x = num-1/3, y = close, yend = close, xend = num+1/3))
      #   }
      #   
      #   return(p)
      # })
      
      # 輸出表格
      output$stockData <- DT::renderDataTable({
        datatable(stockPriceData %>% arrange(desc(date)))
      })
    }
  })
}

# Run the application 
shinyApp(ui = ui, server = server)