R Shiny ile İnteraktif Dashboard Tasarlama

Kullanıcıların filtrelerle veriyi keşfedebildiği, grafik ve özetlerin seçimlere göre anında güncellendiği bir panoyu adım adım kuruyorum: reaktif akış, boş durum yönetimi ve paylaşıma hazırlık.

  • Araçlar
  • Veri Görselleştirme

Shiny nasıl çalışır?

Shiny, R koduyla web uygulamaları oluşturmayı sağlar. Bir uygulama tipik olarak üç parçadan oluşur: UI arayüzü ve kontrolleri tanımlar, server girdilere göre hesaplama yapıp çıktıları üretir, shinyApp() ise bu ikisini birleştirip uygulamayı başlatır.

Kullanıcı bir seçim yaptığında Shiny hangi çıktının o girdiye bağlı olduğunu izler ve yalnızca gereken ifadeleri yeniden çalıştırır. Bu reaktif akış sayesinde filtreler, grafikler, KPI metinleri ve tablolar aynı seçime birlikte uyum sağlar — hiçbirini elle güncellemeniz gerekmez.

Dashboard tasarım ilkesi: önce kullanıcının hangi kararı vereceğini veya hangi soruyu yanıtlamak istediğini belirleyin; sonra yalnızca o soruyu destekleyen filtre ve görselleri ekleyin. “Bir de şunu koyalım” ile büyüyen panolar kimsenin kullanmadığı panolardır.

1. Kapsamı ve veri yapısını planlayın

İlk sürümde tek bir kullanıcı profili ve birkaç net soruyla başlayın. Örneğin bir ekip “hangi silindir grubunda yakıt verimliliği daha iyi?” sorusunu yanıtlamak isteyebilir. Veri alanlarını, filtre değerlerini, yenileme sıklığını ve gösterilecek metrikleri önceden belirleyin.

Veri kaynağını uygulamadan ayrı tutun ve büyük dosyaları her kullanıcı oturumunda yeniden okumayın. Üretim senaryosunda önbellekleme, veritabanı sorgusu, hata yönetimi ve erişim kontrolü de tasarımın parçasıdır.

2. Arayüzü kurun

Filtreleri mantıklı biçimde gruplayın: sol panelde kategori seçimi, tarih aralığı ve ölçüt seçicisi; ana alanda KPI, grafik ve tablo. Kontrol etiketleri anlaşılır olmalı ve başlangıç seçimi tek başına yararlı bir görünüm üretmeli.

UI tanımı · app.R
library(shiny)
library(ggplot2)
library(dplyr)

ui <- fluidPage(
  titlePanel("Araç Verisi Dashboard'u"),
  sidebarLayout(
    sidebarPanel(
      selectInput("cyl", "Silindir grubu",
                  choices = c("Tümü", "4", "6", "8")),
      selectInput("x", "X ekseni",
                  choices = c("mpg", "wt", "hp"), selected = "wt"),
      selectInput("y", "Y ekseni",
                  choices = c("mpg", "wt", "hp"), selected = "mpg")
    ),
    mainPanel(
      textOutput("n"),
      plotOutput("scatter"),
      tableOutput("summary")
    )
  )
)

inputId değerleri server tarafında input$... olarak okunur. UI'daki her çıktı kimliğinin server tarafında karşılık gelen bir render* ifadesi olmalı; eşleşmeyen bir kimlik hata vermez, sadece boş bir alan bırakır.

3. Filtreleme ve reaktif hesaplama

Tekrarlanan filtrelemeyi reactive() içinde tanımlayın. Böylece veri alt kümesi yalnızca bağımlı girdi değiştiğinde yeniden hesaplanır ve birden çok çıktı aynı sonucu paylaşır. Aynı filtreyi üç ayrı render bloğunda tekrarlamak, aynı işi üç kez yaptırmak demektir.

Reaktif veri kümesi ve çıktılar
cars <- mtcars |>
  tibble::rownames_to_column("model") |>
  mutate(cyl = as.character(cyl))

server <- function(input, output, session) {

  filtered <- reactive({
    if (input$cyl == "Tümü") cars
    else filter(cars, cyl == input$cyl)
  })

  output$n <- renderText({
    paste("Filtredeki kayıt:", nrow(filtered()))
  })

  output$scatter <- renderPlot({
    ggplot(filtered(), aes(x = .data[[input$x]],
                           y = .data[[input$y]])) +
      geom_point(color = "#48b8c6", size = 3) +
      theme_minimal(base_size = 13) +
      labs(x = input$x, y = input$y)
  })

  output$summary <- renderTable({
    filtered() |>
      summarise(across(c(mpg, wt, hp),
                       ~ round(mean(.x), 2)))
  })
}

shinyApp(ui = ui, server = server)

Dikkat edilecek iki ayrıntı var. filtered bir fonksiyondur, bu yüzden her kullanımda filtered() şeklinde parantezle çağrılır. .data[[input$x]] ise kullanıcının seçtiği sütun adını ggplot'a güvenli biçimde aktarır — doğrudan aes(x = input$x) yazarsanız grafikte verinin kendisi değil, sütunun adı metin olarak çizilir.

4. Grafikleri ve özetleri tasarlayın

Her görsel tek bir soruya cevap versin. Zaman eğilimi için çizgi, kategori kıyası için çubuk, iki ölçüm arasındaki ilişki için saçılım grafiği kullanın. Eksen adları, birimler, başlıklar ve sayı biçimleri açık olmalı.

Dashboard öğesiYanıtladığı soruShiny çıkışı
KPI kartıSeçili filtredeki toplam durum nedir?textOutput / value box
Çizgi grafikZaman içinde ne değişti?plotOutput + renderPlot
TabloDetay kayıtlar veya özet metrikler neler?tableOutput + renderTable
Harita / etkileşimli grafikNerede yoğunlaşıyor, nereye odaklanmalı?htmlwidget çıktıları

Panonun varsayılan hâli de anlamlı olsun. Boş panel, kesilmiş etiket ve aşırı renk kullanımı kullanıcıya ek yük bindirir; renkleri tutarlı ve erişilebilir seçin.

5. Hata ve boş durumları yönetin

Filtre seçimi hiçbir kayıt döndürmeyebilir. Çıktıyı hesaplamadan önce req() ile gerekli girdileri doğrulayın ya da anlaşılır bir boş durum mesajı gösterin. Sayısal metriklerde NA değerlerini nasıl ele aldığınızı belirtin — her eksik değeri sessizce sıfıra çevirmek, kullanıcıya olmayan bir gerçeklik sunar.

Girdiyi ve boş sonucu ele almak
output$summary <- renderTable({
  dat <- filtered()

  validate(need(nrow(dat) > 0,
                "Bu filtrelerle kayıt bulunamadı."))

  summarise(dat, ortalama_mpg = mean(mpg, na.rm = TRUE))
})

validate(need(...)) kullanıcıya kırmızı bir R hatası yerine yazdığınız cümleyi gösterir. Veri yükleme hatalarını, uzun süren işlemleri ve son güncelleme zamanını da görünür kılın; kullanıcı bir şeyi yanlış anladığında nedenini kendisi bulabilsin.

6. Performans ve uygulama yapısı

Reaktif ifadeleri küçük ve yeniden kullanılabilir tutun. Ağır hesaplamaları her çıktıda yeniden yazmak yerine ortak bir reaktif sonuçtan besleyin. Büyük veride filtrelemeyi veritabanına itmeyi, önbelleklemeyi veya önceden hazırlanmış özet tabloları değerlendirin.

Uygulama büyüdükçe filtre, grafik ve KPI bileşenlerini Shiny modüllerine ayırın; veri erişimi, iş mantığı ve sunum kodunu birbirine karıştırmayın. Gizli bilgileri (bağlantı dizesi, anahtar, parola) asla kaynak koda gömmeyin ve paylaşıma açmadan önce erişim modelini netleştirin.

7. Geliştirmeden paylaşıma

Altı adım
  1. Yerelde
    Çalıştır
  2. Sonuçları
    Doğrula
  3. Ekranlarda
    İncele
  4. Bağımlılıkları
    Kaydet
  5. Dağıtımı
    Seç
  6. İzle ve
    Güncelle
  1. Yerelde çalıştırın: küçük bir örnek veri ve temel filtrelerle başlayın.
  2. Beklenen sonuçları doğrulayın: filtreli grafik ve özetleri bağımsız bir hesapla karşılaştırın.
  3. Farklı ekranlarda inceleyin: dar mobil ekranlarda kontrolleri ve grafikleri deneyin.
  4. Bağımlılıkları kaydedin: R paket sürümlerini ve veri gereksinimlerini yazılı tutun.
  5. Dağıtım ortamını seçin: kurum içi sunucu ya da uygun bir Shiny barındırma seçeneği.
  6. İzleyin ve güncelleyin: hata, veri tazeliği, erişim ve performansı takip edin.

Dashboard kontrol listesi

1

Kullanıcı ve soru

Panonun kime hizmet ettiği ve hangi kararı desteklediği tanımlı olmalı.

2

Anlaşılır filtreler

Etiketler kullanıcının dilinde olsun; sütun adı değil, iş terimi yazsın.

3

Yararlı varsayılan

Hiçbir seçim yapılmadan açıldığında da anlamlı bir görünüm sunsun.

4

Her çıktı bir soru

Hangi soruya cevap verdiğini söyleyemediğiniz görseli panodan çıkarın.

5

Boş durum

Sonuç dönmeyen filtrelerde açıklayıcı mesaj gösterilsin.

6

Tazelik ve erişim

Verinin ne zaman güncellendiği ve kimin görebildiği belli olsun.

Küçük bir arayüzle başlayın

Bir Shiny panosunun değeri, kullanıcı girdilerinin veriyi keşfetmeyi gerçekten kolaylaştırmasından gelir. Küçük bir arayüzle başlayın, girdileri reaktif veri ve çıktılara bağlayın, sonra gerçek kullanım geri bildirimiyle geliştirin.

Temiz bir düzen, doğru seçilmiş metrikler ve açıklayıcı boş durumlar; çoğu zaman eklenen fazladan görsel sayısından çok daha değerlidir. Kullanılmayan her kontrol, kullanılan kontrollerin de dikkatini dağıtır.

Resmî kaynaklar

How does Shiny work?

Shiny lets you build web applications with R code. An app typically has three parts: the UI defines the interface and controls, the server computes and produces outputs from the inputs, and shinyApp() combines the two and launches the application.

When a user makes a selection, Shiny tracks which outputs depend on that input and re-runs only the expressions that need it. Thanks to this reactive flow, filters, charts, KPI text and tables all adapt to the same selection together — you never update any of them by hand.

A design principle: decide first which decision the user needs to make, or which question they want answered; then add only the filters and visuals that serve that question. Dashboards that grow through “let's add this too” are dashboards nobody uses.

1. Plan the scope and the data

Start the first version with a single user profile and a few clear questions. A team might want to answer “which cylinder group has better fuel efficiency?”. Decide the data fields, filter values, refresh frequency and the metrics you will show in advance.

Keep the data source separate from the app and do not re-read large files on every user session. In a production scenario, caching, database queries, error handling and access control are part of the design too.

2. Build the interface

Group the filters sensibly: category selection, date range and metric picker in the side panel; KPIs, charts and tables in the main area. Control labels should be understandable, and the default selection alone should produce a useful view.

The UI definition · app.R
library(shiny)
library(ggplot2)
library(dplyr)

ui <- fluidPage(
  titlePanel("Vehicle Data Dashboard"),
  sidebarLayout(
    sidebarPanel(
      selectInput("cyl", "Cylinder group",
                  choices = c("All", "4", "6", "8")),
      selectInput("x", "X axis",
                  choices = c("mpg", "wt", "hp"), selected = "wt"),
      selectInput("y", "Y axis",
                  choices = c("mpg", "wt", "hp"), selected = "mpg")
    ),
    mainPanel(
      textOutput("n"),
      plotOutput("scatter"),
      tableOutput("summary")
    )
  )
)

The inputId values are read on the server side as input$.... Every output id in the UI needs a matching render* expression on the server; an unmatched id raises no error, it just leaves an empty space.

3. Filtering and reactive computation

Define repeated filtering inside reactive(). The data subset is then recomputed only when a dependent input changes, and several outputs share the same result. Repeating the same filter in three separate render blocks means doing the same work three times.

A reactive dataset and its outputs
cars <- mtcars |>
  tibble::rownames_to_column("model") |>
  mutate(cyl = as.character(cyl))

server <- function(input, output, session) {

  filtered <- reactive({
    if (input$cyl == "All") cars
    else filter(cars, cyl == input$cyl)
  })

  output$n <- renderText({
    paste("Records in filter:", nrow(filtered()))
  })

  output$scatter <- renderPlot({
    ggplot(filtered(), aes(x = .data[[input$x]],
                           y = .data[[input$y]])) +
      geom_point(color = "#48b8c6", size = 3) +
      theme_minimal(base_size = 13) +
      labs(x = input$x, y = input$y)
  })

  output$summary <- renderTable({
    filtered() |>
      summarise(across(c(mpg, wt, hp),
                       ~ round(mean(.x), 2)))
  })
}

shinyApp(ui = ui, server = server)

Two details are worth noting. filtered is a function, so it is called with parentheses — filtered() — wherever it is used. And .data[[input$x]] passes the user's chosen column name safely into ggplot; write aes(x = input$x) directly and the chart plots the column's name as text rather than the data itself.

4. Design the charts and summaries

Let each visual answer one question. Use a line for trends over time, bars for comparing categories, a scatter for the relationship between two measures. Axis names, units, titles and number formats should all be explicit.

Dashboard elementQuestion it answersShiny output
KPI cardWhat is the total under the current filter?textOutput / value box
Line chartWhat changed over time?plotOutput + renderPlot
TableWhat are the detail records or summary metrics?tableOutput + renderTable
Map / interactive chartWhere is it concentrated, where should attention go?htmlwidget outputs

The default state of the dashboard should be meaningful too. Empty panels, truncated labels and excessive colour all add load for the user; keep colours consistent and accessible.

5. Handle errors and empty states

A filter selection may return no records at all. Validate the required inputs with req() before computing, or show a clear empty-state message. State how you handle NA values in numeric metrics — quietly turning every missing value into zero presents the user with a reality that does not exist.

Handling input and empty results
output$summary <- renderTable({
  dat <- filtered()

  validate(need(nrow(dat) > 0,
                "No records match these filters."))

  summarise(dat, mean_mpg = mean(mpg, na.rm = TRUE))
})

validate(need(...)) shows the user the sentence you wrote instead of a red R error. Make data-loading failures, long-running operations and the last refresh time visible too, so that when a user misreads something they can work out why.

6. Performance and app structure

Keep reactive expressions small and reusable. Rather than repeating heavy computation in every output, feed them all from one shared reactive result. On large data, consider pushing filtering down to the database, caching, or preparing smaller summary tables.

As the app grows, split filters, charts and KPI components into Shiny modules, and do not mix data access, business logic and presentation code. Never embed secrets — connection strings, keys, passwords — in the source, and settle the access model before sharing.

7. From development to sharing

Six steps
  1. Run
    Locally
  2. Verify
    Results
  3. Check on
    Screens
  4. Record
    Dependencies
  5. Choose
    Deployment
  6. Monitor &
    Update
  1. Run it locally: start with a small sample dataset and the basic filters.
  2. Verify the expected results: compare filtered charts and summaries against an independent calculation.
  3. Check it on different screens: try the controls and charts on narrow mobile viewports.
  4. Record the dependencies: keep R package versions and data requirements written down.
  5. Choose the deployment environment: an internal server or a suitable Shiny hosting option.
  6. Monitor and update: track errors, data freshness, access and performance.

A dashboard checklist

1

User and question

Who the dashboard serves and which decision it supports must be defined.

2

Clear filters

Labels in the user's language — a business term, not a column name.

3

Useful default

It should show something meaningful before any selection is made.

4

One question per output

Remove any visual whose question you cannot state out loud.

5

Empty states

Filters returning nothing should show an explanatory message.

6

Freshness and access

When the data was refreshed and who can see it should both be clear.

Start with a small interface

A Shiny dashboard earns its value when the user's inputs genuinely make the data easier to explore. Start with a small interface, wire the inputs to reactive data and outputs, then improve it with feedback from real use.

A clean layout, well-chosen metrics and explanatory empty states are usually worth far more than extra visuals. Every unused control also distracts from the ones that are used.

Official resources